mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 07:37:25 -05:00
Compare commits
20 Commits
v0.6.0-alp
...
v0.5.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59e74d70b4 | ||
|
|
8d6b7c3ac8 | ||
|
|
372d2ff870 | ||
|
|
32a8363b45 | ||
|
|
87f1f8986f | ||
|
|
b3c08053fb | ||
|
|
a5208dcf1e | ||
|
|
2782953518 | ||
|
|
366055145e | ||
|
|
39ebdb47b1 | ||
|
|
c788292db8 | ||
|
|
f0dc80437c | ||
|
|
2d6c5c2331 | ||
|
|
bc4e6cb5b0 | ||
|
|
d69ad46c10 | ||
|
|
ba7b616f88 | ||
|
|
8355b7e3b9 | ||
|
|
22ffb656be | ||
|
|
de80d2a6c1 | ||
|
|
a320b4a24f |
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help us improve sdrtrunk
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**sdrtrunk Version**
|
||||
Indicate which sdrtrunk version you are using. Use 'master-branch' if you are running the latest code.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Application Log**
|
||||
If applicable, include either the full application log,or a snippet of the error logging.
|
||||
|
||||
**Desktop (optional - complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- CPU Cores: [e.g. 4 cores]
|
||||
- RAM:[e.g. 16GB]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
16
.github/ISSUE_TEMPLATE/how-to-question-or-does-the-software-support-xxx.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/how-to-question-or-does-the-software-support-xxx.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: How-To Question or Does The Software Support xxx
|
||||
about: End user question or support discussion
|
||||
title: STOP - Don't Use Issue Reporting to Post End-User Questions!!!
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
STOP !!!!
|
||||
|
||||
GitHub issues are for reporting software bugs or requesting enhancements.
|
||||
|
||||
If you have a question about how to use the software, or if you want to know if sdrtrunk supports a protocol, tuner, or other aspect, please post your question in one of the Support forums.
|
||||
|
||||
Visit: https://github.com/DSheirer/sdrtrunk/wiki
|
||||
263
build.gradle
263
build.gradle
@@ -2,7 +2,7 @@ import java.text.SimpleDateFormat
|
||||
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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
|
||||
@@ -18,6 +18,29 @@ import java.text.SimpleDateFormat
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instructions for building/compiling the sdrtrunk application.
|
||||
*
|
||||
* Prerequisites:
|
||||
* Install and configure an OpenJDK version 19+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
|
||||
* - Optional: install and configure Gradle 7.6+
|
||||
*
|
||||
* Scenario 1: run the application via gradle command line from the source code root directory:
|
||||
* command: ./gradlew run
|
||||
*
|
||||
* Scenario 2: create an application release for the current operating system
|
||||
* command: ./gradlew runtimeZipCurrent
|
||||
* Note: release image is located in the /build/image/ directory
|
||||
*
|
||||
* Scenario 3: create releases for Linux and OSX operating systems using downloaded JDKs
|
||||
* command: ./gradlew runtimeZipOthers
|
||||
* Note: release images are located in the /build/image/ directory
|
||||
*
|
||||
* Scenario 4: create release for Windows operating system using downloaded JDK
|
||||
* command: ./gradlew runtimeZipWindows
|
||||
* Note: release image is located in the /build/image/ directory
|
||||
*/
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'java'
|
||||
@@ -30,7 +53,7 @@ repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.6.0-alpha2'
|
||||
version = '0.6.0-alpha5'
|
||||
|
||||
//Java 19 is required for this version of the Project Panama preview/incubator feature
|
||||
java {
|
||||
@@ -98,6 +121,8 @@ dependencies {
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
}
|
||||
|
||||
def os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem
|
||||
|
||||
/**
|
||||
* This is needed for the JDK19 panama and vector API ... until it moves out of incubation or preview
|
||||
*/
|
||||
@@ -106,24 +131,35 @@ tasks.withType(JavaCompile) {
|
||||
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||
applicationDefaultJvmArgs =
|
||||
/**
|
||||
* JVM Arguments for running the application on Windows.
|
||||
*/
|
||||
def jvmArgsWindows =
|
||||
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', //windows users for JIDE
|
||||
'--add-modules=jdk.incubator.vector', //Needed while Project Panama API is still in incubator
|
||||
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
|
||||
'--enable-native-access=ALL-UNNAMED',
|
||||
'-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64'] //Allows Project Panama Foreign Function access to system library
|
||||
|
||||
/**
|
||||
* JVM Arguments for running the application on Linux.
|
||||
*/
|
||||
def jvmArgsLinux =
|
||||
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||
'--add-modules=jdk.incubator.vector', //Needed while Project Panama API is still in incubator
|
||||
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
|
||||
'--enable-native-access=ALL-UNNAMED'] //Allows Project Panama Foreign Function access to system library
|
||||
'--enable-native-access=ALL-UNNAMED',
|
||||
'-Djava.library.path=/usr/local/lib'] //Allows Project Panama Foreign Function access to system library
|
||||
|
||||
application {
|
||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||
|
||||
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
|
||||
if(os.isWindows()) {
|
||||
//Replace the default Java system library path with an explicit location for the SDRplay API library (Windows)
|
||||
applicationDefaultJvmArgs.add('-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64')
|
||||
//On windows, we also need to export the windows swing look & feel module
|
||||
applicationDefaultJvmArgs.add('--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED')
|
||||
applicationDefaultJvmArgs = jvmArgsWindows
|
||||
}
|
||||
else {
|
||||
//Replace the default Java system library path with an explicit location for the SDRplay API library (Linux/OS-X)
|
||||
applicationDefaultJvmArgs.add("-Djava.library.path=/usr/local/lib")
|
||||
applicationDefaultJvmArgs = jvmArgsLinux
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,85 +203,152 @@ String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
|
||||
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
|
||||
|
||||
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
|
||||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
|
||||
/**
|
||||
* Download URLs to download the JDK as part of the gradle build packaging process
|
||||
*/
|
||||
def jdk_download_base = "https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-"
|
||||
def jdk_download_suffix = "-full.tar.gz"
|
||||
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
|
||||
def jdk_download_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix
|
||||
def jdk_download_osx_x86_64 = jdk_download_base + "macos-aarch64" + jdk_download_suffix
|
||||
def jdk_download_osx_aarch64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
|
||||
def jdk_download_windows_x86_64 = jdk_download_base + "windows-amd64-full.zip"
|
||||
|
||||
/**
|
||||
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
|
||||
* including this option: '-PdownloadJdks=true'
|
||||
* Configures the runtime zip task with additional options/settings.
|
||||
* @param rt task to configure
|
||||
* @param jvmArgs to use (windows or linux)
|
||||
* @return configured task.
|
||||
*/
|
||||
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
|
||||
|
||||
/**
|
||||
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
|
||||
*
|
||||
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
|
||||
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
|
||||
*
|
||||
* >: gradle runtimeZip
|
||||
*
|
||||
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
|
||||
*
|
||||
* >: gradle runtimeZip -PdownloadJdks=true
|
||||
*/
|
||||
runtime {
|
||||
//Build by automatically downloading JDKs for the OS and CPU targets
|
||||
if(downloadJdks) {
|
||||
println("Building all runtime images")
|
||||
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
|
||||
targetPlatform(targetLinuxAarch64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetLinuxX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetOsxAarch64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetOsxX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
|
||||
}
|
||||
targetPlatform(targetWindowsX86_64) {
|
||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
|
||||
}
|
||||
}
|
||||
//Build using pre-downloaded JDKs
|
||||
else if(hasTargetJdks) {
|
||||
println("Building all runtime images using pre-downloaded JDKs")
|
||||
if(file(jdk_linux_aarch64).exists()) {
|
||||
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
|
||||
}
|
||||
if(file(jdk_linux_x86_64).exists()) {
|
||||
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
|
||||
}
|
||||
if(file(jdk_osx_aarch64).exists()) {
|
||||
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
|
||||
}
|
||||
if(file(jdk_osx_x86_64).exists()) {
|
||||
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
|
||||
}
|
||||
if(file(jdk_windows_x86_64).exists()) {
|
||||
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
|
||||
}
|
||||
}
|
||||
//Build using locally installed JDK only
|
||||
else {
|
||||
println("Building a single runtime image using locally installed JDK")
|
||||
}
|
||||
def configure(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
|
||||
//Use the correct set of JVM arguments
|
||||
rt.extension.launcherData.get().jvmArgs = jvmArgs
|
||||
|
||||
//Additional modules to include with auto-detected modules.
|
||||
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
|
||||
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
|
||||
//jdk.accessibility is used with assistive technologies like screen readers
|
||||
modules = ['jdk.crypto.ec','jdk.incubator.vector','jdk.accessibility']
|
||||
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility')
|
||||
|
||||
//Use auto-detected modules and 'add' any specified modules.
|
||||
additive = true
|
||||
rt.extension.additive.set(true)
|
||||
|
||||
//Recommended options for reducing/shrinking runtime target image size
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
rt.extension.addOptions('--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages')
|
||||
|
||||
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
||||
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
|
||||
file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
||||
rt.extension.imageZip.set(file("$buildDir/image/sdr-trunk.zip"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a release package for the current operating system.
|
||||
*
|
||||
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||
*
|
||||
* Usage: gradle runtimeZipCurrent
|
||||
* Usage: gradlew runtimeZipCurrent (windows - using gradlew)
|
||||
* Usage: ./gradlew runtimeZipCurrent (linux - using gradlew)
|
||||
*/
|
||||
tasks.register('runtimeZipCurrent', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||
def arch = System.properties.getProperty("os.arch")
|
||||
def javaHome = org.gradle.internal.jvm.Jvm.current().getJavaHome().toString()
|
||||
|
||||
if(os.isWindows()) {
|
||||
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
|
||||
configure(rt, jvmArgsWindows)
|
||||
}
|
||||
else if(os.isLinux())
|
||||
{
|
||||
if(arch.equals("amd64")) {
|
||||
rt.extension.targetPlatform(targetLinuxX86_64, javaHome)
|
||||
}
|
||||
else if(arch.equals("aarch64")) {
|
||||
rt.extension.targetPlatform(targetLinuxAarch64, javaHome)
|
||||
}
|
||||
configure(rt, jvmArgsLinux)
|
||||
}
|
||||
else if(os.isMacOsX())
|
||||
{
|
||||
if(arch.equals("amd64")) {
|
||||
rt.extension.targetPlatform(targetOsxX86_64, javaHome)
|
||||
}
|
||||
else if(arch.equals("aarch64")) {
|
||||
rt.extension.targetPlatform(targetOsxAarch64, javaHome)
|
||||
}
|
||||
configure(rt, jvmArgsLinux)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Windows release package.
|
||||
*
|
||||
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||
*
|
||||
* Usage: gradle runtimeZipWindows
|
||||
* Usage: gradlew runtimeZipWindows (windows - using gradlew)
|
||||
* Usage: ./gradlew runtimeZipWindows (linux - using gradlew)
|
||||
*/
|
||||
tasks.register('runtimeZipWindows', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||
if(file(jdk_windows_x86_64).exists()) {
|
||||
rt.extension.targetPlatform(targetWindowsX86_64, jdk_windows_x86_64, [])
|
||||
}
|
||||
else {
|
||||
rt.extension.targetPlatform(targetWindowsX86_64) {
|
||||
jdkHome = jdkDownload(jdk_download_windows_x86_64)
|
||||
}
|
||||
}
|
||||
|
||||
configure(rt, jvmArgsWindows)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Linux & OSX release packages.
|
||||
*
|
||||
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||
*
|
||||
* Usage: gradle runtimeZipOthers
|
||||
* Usage: gradlew runtimeZipOthers (windows - using gradlew)
|
||||
* Usage: ./gradlew runtimeZipOthers (linux - using gradlew)
|
||||
*/
|
||||
tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||
//Linux aarch64
|
||||
if(file(jdk_linux_aarch64).exists()) {
|
||||
rt.extension.targetPlatform(targetLinuxAarch64, jdk_linux_aarch64, [])
|
||||
}
|
||||
else {
|
||||
rt.extension.targetPlatform(targetLinuxAarch64) {
|
||||
jdkHome = jdkDownload(jdk_download_linux_aarch64)
|
||||
}
|
||||
}
|
||||
//Linux x86-64
|
||||
if(file(jdk_linux_x86_64).exists()) {
|
||||
rt.extension.targetPlatform(targetLinuxX86_64, jdk_linux_x86_64, [])
|
||||
}
|
||||
else {
|
||||
rt.extension.targetPlatform(targetLinuxX86_64) {
|
||||
jdkHome = jdkDownload(jdk_download_linux_x86_64)
|
||||
}
|
||||
}
|
||||
//OSX aarch64
|
||||
if(file(jdk_osx_aarch64).exists()) {
|
||||
rt.extension.targetPlatform(targetOsxAarch64, jdk_osx_aarch64, [])
|
||||
}
|
||||
else {
|
||||
rt.extension.targetPlatform(targetOsxAarch64) {
|
||||
jdkHome = jdkDownload(jdk_download_osx_aarch64)
|
||||
}
|
||||
}
|
||||
//Linux x86-64
|
||||
if(file(jdk_osx_x86_64).exists()) {
|
||||
rt.extension.targetPlatform(targetOsxX86_64, jdk_osx_x86_64, [])
|
||||
}
|
||||
else {
|
||||
rt.extension.targetPlatform(targetOsxX86_64) {
|
||||
jdkHome = jdkDownload(jdk_download_osx_x86_64)
|
||||
}
|
||||
}
|
||||
|
||||
configure(rt, jvmArgsLinux);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 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.audio;
|
||||
@@ -106,7 +105,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
|
||||
* listener(s).
|
||||
*/
|
||||
protected AudioSegment getAudioSegment()
|
||||
public AudioSegment getAudioSegment()
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
@@ -135,7 +134,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
}
|
||||
}
|
||||
|
||||
protected void addAudio(float[] audioBuffer)
|
||||
public void addAudio(float[] audioBuffer)
|
||||
{
|
||||
AudioSegment audioSegment = getAudioSegment();
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* 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 the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* 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.
|
||||
* 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/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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.audio;
|
||||
|
||||
@@ -26,15 +29,6 @@ import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
@@ -43,6 +37,14 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
|
||||
@@ -138,6 +140,15 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
||||
return mComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this audio segment is completed.
|
||||
* @return true if completed.
|
||||
*/
|
||||
public boolean isComplete()
|
||||
{
|
||||
return mComplete.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
|
||||
* an audio segment is a duplicate.
|
||||
|
||||
@@ -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.audio.codec.mbe;
|
||||
@@ -32,11 +29,6 @@ import io.github.dsheirer.message.IMessageListener;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import jmbe.iface.IAudioCodec;
|
||||
import jmbe.iface.IAudioCodecLibrary;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@@ -44,15 +36,20 @@ import java.net.URLClassLoader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import jmbe.iface.IAudioCodec;
|
||||
import jmbe.iface.IAudioCodecLibrary;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
|
||||
ISquelchStateListener
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
|
||||
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
|
||||
private static List<String> mLibraryLoadStatusLogged = new ArrayList<>();
|
||||
private static final List<String> mLibraryLoadStatusLogged = new ArrayList<>();
|
||||
private IAudioCodec mAudioCodec;
|
||||
private UserPreferences mUserPreferences;
|
||||
private final UserPreferences mUserPreferences;
|
||||
private static Class sLoadedJmbeAudioConverterClass;
|
||||
|
||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
||||
{
|
||||
@@ -70,7 +67,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
mAudioCodec = null;
|
||||
}
|
||||
|
||||
protected IAudioCodec getAudioCodec()
|
||||
public IAudioCodec getAudioCodec()
|
||||
{
|
||||
return mAudioCodec;
|
||||
}
|
||||
@@ -110,38 +107,67 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
protected abstract String getCodecName();
|
||||
|
||||
/**
|
||||
* Loads audio frame processing chain. Constructs an imbe targetdataline
|
||||
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
|
||||
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
|
||||
* Loads JMBE audio converter library class and then instantiates new converter instances from the loaded class.
|
||||
*/
|
||||
protected void loadConverter()
|
||||
{
|
||||
IAudioCodec audioConverter = null;
|
||||
|
||||
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
||||
|
||||
if(path != null)
|
||||
if(sLoadedJmbeAudioConverterClass == null)
|
||||
{
|
||||
try
|
||||
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
||||
|
||||
if(path != null)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.info("Loading JMBE library from [" + path.toString() + "]");
|
||||
mLog.info("Loading JMBE library from [" + path + "]");
|
||||
}
|
||||
|
||||
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
||||
this.getClass().getClassLoader());
|
||||
|
||||
Class classToLoad = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
||||
|
||||
Object instance = classToLoad.getDeclaredConstructor().newInstance();
|
||||
|
||||
if(instance instanceof IAudioCodecLibrary)
|
||||
try
|
||||
{
|
||||
IAudioCodecLibrary library = (IAudioCodecLibrary)instance;
|
||||
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
||||
this.getClass().getClassLoader());
|
||||
|
||||
sLoadedJmbeAudioConverterClass = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
|
||||
}
|
||||
}
|
||||
catch(MalformedURLException mue)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
catch(ClassNotFoundException e1)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - class not found");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(sLoadedJmbeAudioConverterClass != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object instance = sLoadedJmbeAudioConverterClass.getDeclaredConstructor().newInstance();
|
||||
|
||||
if(instance instanceof IAudioCodecLibrary library)
|
||||
{
|
||||
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
|
||||
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
||||
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
||||
{
|
||||
audioConverter = library.getAudioConverter(getCodecName());
|
||||
|
||||
@@ -169,38 +195,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
|
||||
}
|
||||
}
|
||||
catch(NoSuchMethodException nsme)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
catch(MalformedURLException mue)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
catch(ClassNotFoundException e1)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - class not found");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
catch(InvocationTargetException ite)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
@@ -225,6 +219,14 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
catch(NoSuchMethodException nsme)
|
||||
{
|
||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||
{
|
||||
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
|
||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -235,13 +237,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
}
|
||||
}
|
||||
|
||||
if(audioConverter != null)
|
||||
{
|
||||
mAudioCodec = audioConverter;
|
||||
}
|
||||
else
|
||||
{
|
||||
mAudioCodec = null;
|
||||
}
|
||||
mAudioCodec = audioConverter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,46 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.audio.codec.mbe;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
||||
import io.github.dsheirer.module.decode.p25.audio.P25P1CallSequenceRecorder;
|
||||
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
||||
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import jmbe.iface.IAudioCodec;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.DirectoryFileFilter;
|
||||
import org.apache.commons.io.filefilter.SuffixFileFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -32,79 +51,130 @@ public class MBECallSequenceConverter
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
|
||||
|
||||
// public static void convert(Path input, Path output) throws IOException
|
||||
// {
|
||||
// InputStream inputStream = Files.newInputStream(input);
|
||||
// ObjectMapper mapper = new ObjectMapper();
|
||||
// MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
||||
// convert(sequence, output);
|
||||
// }
|
||||
//
|
||||
// public static void convert(MBECallSequence callSequence, Path outputPath)
|
||||
// {
|
||||
// if(callSequence == null || callSequence.isEncrypted())
|
||||
// {
|
||||
// throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
||||
// }
|
||||
//
|
||||
// if(callSequence != null && !callSequence.isEncrypted())
|
||||
// {
|
||||
// ThumbDv.AudioProtocol protocol = ThumbDv.AudioProtocol.P25_PHASE2;
|
||||
//
|
||||
// AudioPacketWaveRecorder recorder = new AudioPacketWaveRecorder(outputPath);
|
||||
// recorder.start();
|
||||
//
|
||||
// long delayMillis = 0;
|
||||
//
|
||||
// try(ThumbDv thumbDv = new ThumbDv(protocol, recorder))
|
||||
// {
|
||||
// thumbDv.start();
|
||||
// for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
||||
// {
|
||||
// mLog.debug("Frame [" + voiceFrame.getFrame() + "] + Hex [" + AmbeResponse.toHex(voiceFrame.getFrameBytes()) + "]");
|
||||
// thumbDv.decode(voiceFrame.getFrameBytes());
|
||||
// delayMillis += 30;
|
||||
// }
|
||||
//
|
||||
// if(delayMillis > 0)
|
||||
// {
|
||||
// delayMillis += 1000;
|
||||
// try
|
||||
// {
|
||||
// Thread.sleep(delayMillis);
|
||||
// }
|
||||
// catch(InterruptedException ie)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch(IOException ioe)
|
||||
// {
|
||||
// mLog.error("Error", ioe);
|
||||
// }
|
||||
//
|
||||
// recorder.stop(Paths.get(outputPath.toString().replace(".tmp", ".wav")), new WaveMetadata());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public static void main(String[] args)
|
||||
// {
|
||||
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_3_TS0_65084_6591007.mbe";
|
||||
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_2_TS1_65035.mbe";
|
||||
//
|
||||
// Path input = Paths.get(mbe);
|
||||
// Path output = Paths.get(mbe.replace(".mbe", ".tmp"));
|
||||
//
|
||||
// mLog.info("Converting: " + mbe);
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// MBECallSequenceConverter.convert(input, output);
|
||||
// }
|
||||
// catch(IOException ioe)
|
||||
// {
|
||||
// mLog.error("Error", ioe);
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* Converts the input MBE file to PCM audio and writes to the output wave file.
|
||||
* @param input path to the MBE file.
|
||||
* @param output path to write the WAVE recording.
|
||||
* @throws IOException if there is an error.
|
||||
*/
|
||||
public static void convert(Path input, Path output) throws IOException
|
||||
{
|
||||
InputStream inputStream = Files.newInputStream(input);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
||||
convert(sequence, output);
|
||||
}
|
||||
|
||||
public static void convert(MBECallSequence callSequence, Path outputPath)
|
||||
{
|
||||
if(callSequence == null || callSequence.isEncrypted())
|
||||
{
|
||||
throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
||||
}
|
||||
|
||||
if(callSequence != null)
|
||||
{
|
||||
if(callSequence.getProtocol().equals(P25P1CallSequenceRecorder.PROTOCOL))
|
||||
{
|
||||
P25P1AudioModule audioModule = new P25P1AudioModule(new UserPreferences(), new AliasList("mbe generator"));
|
||||
audioModule.setRecordAudio(true);
|
||||
audioModule.start();
|
||||
|
||||
if(callSequence.getFromIdentifier() != null)
|
||||
{
|
||||
int from = 0;
|
||||
|
||||
try
|
||||
{
|
||||
from = Integer.parseInt(callSequence.getFromIdentifier());
|
||||
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25RadioIdentifier.createFrom(from),
|
||||
IdentifierUpdateNotification.Operation.ADD, 0));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
if(callSequence.getToIdentifier() != null)
|
||||
{
|
||||
int to = 0;
|
||||
|
||||
try
|
||||
{
|
||||
to = Integer.parseInt(callSequence.getToIdentifier());
|
||||
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25Talkgroup.create(to),
|
||||
IdentifierUpdateNotification.Operation.ADD, 0));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
IAudioCodec codec = audioModule.getAudioCodec();
|
||||
|
||||
for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
||||
{
|
||||
byte[] frameBytes = voiceFrame.getFrameBytes();
|
||||
float[] audio = codec.getAudio(frameBytes);
|
||||
audioModule.addAudio(audio);
|
||||
}
|
||||
|
||||
AudioSegment audioSegment = audioModule.getAudioSegment();
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.recordWAVE(audioSegment, outputPath);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error writing audio segment, ioe");
|
||||
}
|
||||
|
||||
audioModule.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
boolean all = true;
|
||||
|
||||
String path = "/home/denny/SDRTrunk/recordings";
|
||||
Path input = Paths.get(path);
|
||||
|
||||
if(all)
|
||||
{
|
||||
Collection<File> mbeFiles = FileUtils.listFiles(input.toFile(), new SuffixFileFilter(".mbe"), DirectoryFileFilter.DIRECTORY);
|
||||
|
||||
for(File inputFile: mbeFiles)
|
||||
{
|
||||
Path output = Paths.get(inputFile.getAbsolutePath().replace(".mbe", ".wav"));
|
||||
mLog.info("Converting: " + inputFile);
|
||||
try
|
||||
{
|
||||
MBECallSequenceConverter.convert(inputFile.toPath(), output);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Path output = Paths.get(path.replace(".mbe", ".wav"));
|
||||
mLog.info("Converting: " + path);
|
||||
|
||||
try
|
||||
{
|
||||
MBECallSequenceConverter.convert(input, output);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error", ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,27 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.audio.playback;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.id.priority.Priority;
|
||||
import io.github.dsheirer.audio.AudioEvent;
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.controller.NamingThreadFactory;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
@@ -33,11 +30,13 @@ import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -51,10 +50,6 @@ import javax.sound.sampled.LineListener;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Audio output/playback channel for a single audio mixer channel. Providers support for playback of audio segments
|
||||
@@ -67,7 +62,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
private int mBufferStopThreshold;
|
||||
private Listener<IdentifierCollection> mIdentifierCollectionListener;
|
||||
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||
private ScheduledFuture<?> mProcessorFuture;
|
||||
private SourceDataLine mOutput;
|
||||
private Mixer mMixer;
|
||||
private MixerChannel mMixerChannel;
|
||||
@@ -76,17 +70,17 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
private AudioEvent mAudioStartEvent;
|
||||
private AudioEvent mAudioStopEvent;
|
||||
private boolean mCanProcessAudio = false;
|
||||
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||
private AudioSegment mCurrentAudioSegment;
|
||||
private AudioSegment mNextAudioSegment;
|
||||
private ReentrantLock mLock = new ReentrantLock();
|
||||
private int mCurrentBufferIndex = 0;
|
||||
private UserPreferences mUserPreferences;
|
||||
private BooleanProperty mEmptyProperty = new SimpleBooleanProperty(true);
|
||||
private IntegerProperty mAudioPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
||||
private ByteBuffer mAudioSegmentStartTone;
|
||||
private ByteBuffer mAudioSegmentPreemptTone;
|
||||
private ByteBuffer mAudioSegmentDropTone;
|
||||
private boolean mRunning = false;
|
||||
private ScheduledExecutorService mScheduledExecutorService;
|
||||
private ScheduledFuture<?> mProcessorFuture;
|
||||
private long mOutputLastTimestamp = 0;
|
||||
private static final long STALE_PLAYBACK_THRESHOLD_MS = 500;
|
||||
|
||||
/**
|
||||
* Single audio channel playback with automatic starting and stopping of the
|
||||
@@ -107,6 +101,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
{
|
||||
mMixer = mixer;
|
||||
mMixerChannel = mixerChannel;
|
||||
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
|
||||
"sdrtrunk audio output " + mixerChannel.name()));
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
try
|
||||
@@ -150,7 +146,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
}
|
||||
|
||||
//Run the queue processor task every 100 milliseconds or 10 times a second
|
||||
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
mProcessorFuture = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
0, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@@ -172,26 +168,17 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean property that indicates if this audio output has an audio segment queued or in process of playback.
|
||||
* Indicates if this audio output doesn't currently have any audio segments queued for playback.
|
||||
* @return true if empty
|
||||
*/
|
||||
public BooleanProperty emptyProperty()
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return mEmptyProperty;
|
||||
return mAudioSegmentQueue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio playback priority of the current audio segment or default audio priority if nothing is currently loaded.
|
||||
*/
|
||||
public IntegerProperty audioPriorityProperty()
|
||||
{
|
||||
return mAudioPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the audio segment for playback.
|
||||
*
|
||||
* Note: if a segment is currently playing and another audio segment is already queued for playback, invoking
|
||||
* this method will overwrite the queued segment with the argument.
|
||||
* Schedules the audio segment for playback. The audio segment user count should already be incremented by the
|
||||
* calling entity.
|
||||
*
|
||||
* @param audioSegment to schedule for playback.
|
||||
*/
|
||||
@@ -199,23 +186,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
{
|
||||
if(audioSegment != null)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
if(mNextAudioSegment != null)
|
||||
{
|
||||
mNextAudioSegment.decrementConsumerCount();
|
||||
mNextAudioSegment = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mNextAudioSegment = audioSegment;
|
||||
mEmptyProperty.set(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
//Audio segment use count has already been incremented by the external caller.
|
||||
mAudioSegmentQueue.add(audioSegment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +234,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
private void updateToneInsertionAudioClips()
|
||||
{
|
||||
mAudioSegmentStartTone = null;
|
||||
mAudioSegmentPreemptTone = null;
|
||||
mAudioSegmentDropTone = null;
|
||||
|
||||
float[] start = mUserPreferences.getPlaybackPreference().getStartTone();
|
||||
@@ -272,13 +243,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
mAudioSegmentStartTone = convert(start);
|
||||
}
|
||||
|
||||
float[] preempt = mUserPreferences.getPlaybackPreference().getPreemptTone();
|
||||
|
||||
if(preempt != null)
|
||||
{
|
||||
mAudioSegmentPreemptTone = convert(preempt);
|
||||
}
|
||||
|
||||
float[] drop = mUserPreferences.getPlaybackPreference().getDropTone();
|
||||
|
||||
if(drop != null)
|
||||
@@ -287,48 +251,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of a processed audio segment and notifies the originator that processing of the segment is complete
|
||||
* by decrementing the consumer count.
|
||||
* @param audioSegment to dispose
|
||||
*/
|
||||
private void dispose(AudioSegment audioSegment)
|
||||
{
|
||||
mAudioPriority.unbind();
|
||||
|
||||
if(audioSegment != null)
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
audioSegment.removeIdentifierUpdateNotificationListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a tone indicating that a new audio segment is starting
|
||||
*/
|
||||
private ByteBuffer getAudioSegmentStartTone()
|
||||
{
|
||||
return mAudioSegmentStartTone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a tone indicating that the current audio segment playback has been preempted for a higher priority
|
||||
* audio segment that is now starting.
|
||||
*/
|
||||
private ByteBuffer getAudioSegmentPreemptionTone()
|
||||
{
|
||||
return mAudioSegmentPreemptTone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a tone indicating that the current audio segment playback has been dropped because the audio segment
|
||||
* has been flagged as Do Not Monitor after playback has started.
|
||||
*/
|
||||
private ByteBuffer getAudioSegmentDropTone()
|
||||
{
|
||||
return mAudioSegmentDropTone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the audio buffer data to the source data line. If the data line is not currently playing, write as
|
||||
* much of the buffer to the data line as will fit, start the dataline, and then finish writing the residual
|
||||
@@ -355,6 +277,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
|
||||
//Top off the buffer and check if we can start it
|
||||
wrote += mOutput.write(buffer.array(), 0, toWrite);
|
||||
mOutputLastTimestamp = System.currentTimeMillis();
|
||||
|
||||
checkStart();
|
||||
}
|
||||
@@ -363,147 +286,135 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
{
|
||||
//This will block until the buffer is fully written to the data line
|
||||
mOutput.write(buffer.array(), wrote, buffer.array().length - wrote);
|
||||
mOutputLastTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of the currently assigned audio segment.
|
||||
*/
|
||||
private void disposeCurrentAudioSegment()
|
||||
{
|
||||
if(mCurrentAudioSegment != null)
|
||||
{
|
||||
mCurrentAudioSegment.decrementConsumerCount();
|
||||
mCurrentAudioSegment.removeIdentifierUpdateNotificationListener(this);
|
||||
mCurrentAudioSegment = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the next audio segment from the queue.
|
||||
*/
|
||||
private void loadNextAudioSegment()
|
||||
{
|
||||
AudioSegment audioSegment = mAudioSegmentQueue.poll();
|
||||
|
||||
boolean verificationInProgress = (audioSegment != null);
|
||||
|
||||
while(verificationInProgress)
|
||||
{
|
||||
if(audioSegment != null)
|
||||
{
|
||||
//Throw away the audio segment if it has been flagged as do not monitor or is duplicate
|
||||
if(isThrowaway(audioSegment))
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
audioSegment = mAudioSegmentQueue.poll();
|
||||
}
|
||||
else
|
||||
{
|
||||
verificationInProgress = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
verificationInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentAudioSegment = audioSegment;
|
||||
|
||||
if(audioSegment != null)
|
||||
{
|
||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||
|
||||
//If we played any amount of the previous audio segment then play the audio segment start. We check this
|
||||
//to avoid repeatedly playing an audio segment start tone on the rare occasion that we have empty segments.
|
||||
if(mCurrentBufferIndex > 0)
|
||||
{
|
||||
playAudio(mAudioSegmentStartTone);
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentBufferIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the audio segment should be thrown away because it is marked as duplicate or do-not-monitor.
|
||||
* @param audioSegment to evaluate
|
||||
* @return true if the audio should be thrown away.
|
||||
*/
|
||||
private boolean isThrowaway(AudioSegment audioSegment)
|
||||
{
|
||||
return audioSegment != null &&
|
||||
(audioSegment.isDoNotMonitor() ||
|
||||
(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference()
|
||||
.isDuplicatePlaybackSuppressionEnabled()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage audio segment playback and process audio segment buffers. This method is designed to be called
|
||||
* by a threaded processor repeatedly to playback the current audio segment and check for and start a newly
|
||||
* assigned audio segment. It also handles starting and stopping the playback source data line to avoid audio
|
||||
* by a threaded processor repeatedly to playback the current audio segment and check for and start newly
|
||||
* added audio segments. It also handles starting and stopping the playback source data line to avoid audio
|
||||
* discontinuities due to buffer underruns.
|
||||
*/
|
||||
private void processAudio()
|
||||
{
|
||||
if(mNextAudioSegment != null)
|
||||
if(mCurrentAudioSegment == null)
|
||||
{
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
//Remove an assigned segment that's subsequently flagged as duplicate or do not monitor before playback
|
||||
if(mNextAudioSegment.isDoNotMonitor() || (mNextAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||
{
|
||||
mNextAudioSegment = null;
|
||||
|
||||
if(mCurrentAudioSegment == null)
|
||||
{
|
||||
mEmptyProperty.set(true);
|
||||
}
|
||||
}
|
||||
//For linked audio segments, allow the linked segment to complete first before assigning the next
|
||||
else if(mNextAudioSegment.isLinked())
|
||||
{
|
||||
if(mCurrentAudioSegment == null)
|
||||
{
|
||||
mCurrentAudioSegment = mNextAudioSegment;
|
||||
mNextAudioSegment = null;
|
||||
mCurrentBufferIndex = 0;
|
||||
|
||||
if(mCurrentAudioSegment != null)
|
||||
{
|
||||
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
|
||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||
}
|
||||
else
|
||||
{
|
||||
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Insert audio segment start or audio priority preemption bonk tone
|
||||
if(mCurrentAudioSegment == null)
|
||||
{
|
||||
playAudio(getAudioSegmentStartTone());
|
||||
}
|
||||
else if(mCurrentBufferIndex > 0 &&
|
||||
(!mCurrentAudioSegment.completeProperty().get() ||
|
||||
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount()))
|
||||
{
|
||||
playAudio(getAudioSegmentPreemptionTone());
|
||||
}
|
||||
else
|
||||
{
|
||||
playAudio(getAudioSegmentStartTone());
|
||||
}
|
||||
|
||||
//Close current audio segment
|
||||
dispose(mCurrentAudioSegment);
|
||||
mCurrentAudioSegment = mNextAudioSegment;
|
||||
mNextAudioSegment = null;
|
||||
mCurrentBufferIndex = 0;
|
||||
|
||||
if(mCurrentAudioSegment != null)
|
||||
{
|
||||
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
|
||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||
}
|
||||
else
|
||||
{
|
||||
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
loadNextAudioSegment();
|
||||
}
|
||||
|
||||
if(mCurrentAudioSegment != null)
|
||||
//Reevaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
|
||||
if(isThrowaway(mCurrentAudioSegment))
|
||||
{
|
||||
//Check for completed audio segment or a segment flagged as duplicate or Do Not Monitor
|
||||
if(mCurrentAudioSegment.isDoNotMonitor() ||
|
||||
(mCurrentAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()) ||
|
||||
(mCurrentAudioSegment.completeProperty().get() && mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||
if(mCurrentBufferIndex > 0)
|
||||
{
|
||||
if(mCurrentAudioSegment.isDoNotMonitor())
|
||||
{
|
||||
playAudio(getAudioSegmentDropTone());
|
||||
}
|
||||
|
||||
dispose(mCurrentAudioSegment);
|
||||
mCurrentAudioSegment = null;
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mNextAudioSegment == null)
|
||||
{
|
||||
mEmptyProperty.set(true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
return;
|
||||
playAudio(mAudioSegmentDropTone);
|
||||
}
|
||||
|
||||
//Process any new buffers that have been added to the audio segment. If a next audio segment gets assigned
|
||||
//while processing, exit the loop so that we can evaluate the next for higher priority preempt. If the next
|
||||
//segment is a linked segment, ignore it so that we can close out the current segment.
|
||||
while(mCurrentAudioSegment != null && (mNextAudioSegment == null || mNextAudioSegment.isLinked()) &&
|
||||
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount() &&
|
||||
!mCurrentAudioSegment.isDoNotMonitor() && !(mCurrentAudioSegment.isDuplicate() &&
|
||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
||||
disposeCurrentAudioSegment();
|
||||
loadNextAudioSegment();
|
||||
}
|
||||
|
||||
while(mCurrentAudioSegment != null && mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount())
|
||||
{
|
||||
try
|
||||
{
|
||||
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
||||
|
||||
if(audioBuffer != null)
|
||||
{
|
||||
ByteBuffer audio = convert(audioBuffer);
|
||||
//This call blocks until all audio bytes are dumped into the data line.
|
||||
playAudio(audio);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error while processing audio for [" + mMixerChannel.name() + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
//Check for completed and fully-played audio segment -- load next audio segment
|
||||
if(mCurrentAudioSegment != null &&
|
||||
mCurrentAudioSegment.isComplete() &&
|
||||
(mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||
{
|
||||
disposeCurrentAudioSegment();
|
||||
}
|
||||
|
||||
checkStop();
|
||||
@@ -523,25 +434,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
}
|
||||
|
||||
mProcessorFuture = null;
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(mNextAudioSegment != null)
|
||||
{
|
||||
mNextAudioSegment.decrementConsumerCount();
|
||||
mNextAudioSegment = null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
dispose(mCurrentAudioSegment);
|
||||
mCurrentAudioSegment = null;
|
||||
|
||||
disposeCurrentAudioSegment();
|
||||
mAudioEventBroadcaster.clear();
|
||||
mIdentifierCollectionListener = null;
|
||||
|
||||
@@ -650,14 +543,28 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
if(mRunning)
|
||||
{
|
||||
//If the output buffer falls below the threshold then drain the output and stop playback
|
||||
if(mOutput.isRunning() && mOutput.available() >= mBufferStopThreshold)
|
||||
if(mOutput.isRunning())
|
||||
{
|
||||
mOutput.drain();
|
||||
mOutput.stop();
|
||||
mRunning = false;
|
||||
if(mOutput.available() >= mBufferStopThreshold)
|
||||
{
|
||||
mOutput.drain();
|
||||
mOutput.stop();
|
||||
mRunning = false;
|
||||
}
|
||||
//Detect when we have a partially filled output that will neither start nor stop and the last write
|
||||
//timestamp was more than the staleness threshold (500 ms) -- stop the output and discard stale contents
|
||||
else if(mOutput.available() < mBufferStopThreshold &&
|
||||
mOutput.available() > mBufferStartThreshold &&
|
||||
(System.currentTimeMillis() - mOutputLastTimestamp >= STALE_PLAYBACK_THRESHOLD_MS))
|
||||
{
|
||||
mOutput.stop();
|
||||
//Discard the stale buffer contents.
|
||||
mOutput.flush();
|
||||
mRunning = false;
|
||||
}
|
||||
}
|
||||
//If output playback stopped on its own because the buffer emptied, then cleanup
|
||||
else if(!mOutput.isRunning())
|
||||
else
|
||||
{
|
||||
mRunning = false;
|
||||
}
|
||||
@@ -735,16 +642,33 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
*/
|
||||
public class AudioSegmentProcessor implements Runnable
|
||||
{
|
||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
if(mProcessing.compareAndSet(false, true))
|
||||
{
|
||||
processAudio();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error while processing audio buffers", t);
|
||||
try
|
||||
{
|
||||
//TODO: Debug hooks - remove after testing
|
||||
if(mMixerChannel.name().equals("LEFT"))
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
else if(mMixerChannel.name().equals("RIGHT"))
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
processAudio();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error while processing audio buffers", t);
|
||||
}
|
||||
|
||||
mProcessing.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 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.audio.playback;
|
||||
|
||||
@@ -24,6 +23,7 @@ import io.github.dsheirer.audio.AudioEvent;
|
||||
import io.github.dsheirer.audio.AudioException;
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.audio.IAudioController;
|
||||
import io.github.dsheirer.controller.NamingThreadFactory;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
@@ -31,17 +31,18 @@ import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages scheduling and playback of audio segments to the local users audio system.
|
||||
@@ -50,8 +51,6 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AudioPlaybackManager.class);
|
||||
|
||||
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
|
||||
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
|
||||
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
||||
new AudioEvent(AudioEvent.Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null);
|
||||
public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE =
|
||||
@@ -64,6 +63,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
||||
private List<AudioSegment> mPendingAudioSegments = new ArrayList<>();
|
||||
private LinkedTransferQueue<AudioSegment> mNewAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||
private ScheduledExecutorService mScheduledExecutorService =
|
||||
Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk audio manager"));
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
@@ -202,29 +203,13 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
//Sort audio segments by playback priority and assign to empty audio outputs
|
||||
if(!mAudioSegments.isEmpty())
|
||||
{
|
||||
//TODO: change this to take audio segment start time into account also
|
||||
mAudioSegments.sort(Comparator.comparingInt(o -> o.monitorPriorityProperty().get()));
|
||||
|
||||
//Assign empty audio outputs first
|
||||
for(AudioOutput audioOutput: mAudioOutputs)
|
||||
{
|
||||
if(audioOutput.emptyProperty().get())
|
||||
{
|
||||
audioOutput.play(mAudioSegments.remove(0));
|
||||
|
||||
if(mAudioSegments.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Preempt ongoing audio segment playback when higher priority audio segments are available
|
||||
if(!mAudioSegments.isEmpty())
|
||||
{
|
||||
for(AudioOutput audioOutput: mAudioOutputs)
|
||||
{
|
||||
if(mAudioSegments.get(0).monitorPriorityProperty().get() < audioOutput.audioPriorityProperty().get())
|
||||
if(audioOutput.isEmpty())
|
||||
{
|
||||
audioOutput.play(mAudioSegments.remove(0));
|
||||
|
||||
@@ -284,7 +269,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
}
|
||||
catch(AudioException ae)
|
||||
{
|
||||
mLog.error("Error changing audio output to [" + configuration.toString() + "]", ae);
|
||||
mLog.error("Error changing audio output to [" + configuration + "]", ae);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,7 +317,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
|
||||
}
|
||||
|
||||
mProcessingTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
mProcessingTask = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
0, 100, TimeUnit.MILLISECONDS);
|
||||
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
|
||||
mMixerChannelConfiguration = entry;
|
||||
@@ -343,7 +328,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
* Current audio playback mixer channel configuration setting.
|
||||
*/
|
||||
@Override
|
||||
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
||||
public MixerChannelConfiguration getMixerChannelConfiguration()
|
||||
{
|
||||
return mMixerChannelConfiguration;
|
||||
}
|
||||
@@ -384,16 +369,23 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
*/
|
||||
public class AudioSegmentProcessor implements Runnable
|
||||
{
|
||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
if(mProcessing.compareAndSet(false, true))
|
||||
{
|
||||
processAudioSegments();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Encountered error while processing audio segments", t);
|
||||
try
|
||||
{
|
||||
processAudioSegments();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Encountered error while processing audio segments", t);
|
||||
}
|
||||
|
||||
mProcessing.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public class BinaryMessage extends BitSet
|
||||
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||
private static final String UTF_8 = "UTF-8";
|
||||
private static final String GB2312 = "GB2312";
|
||||
|
||||
|
||||
/**
|
||||
* Logical (ie constructed) size of this bitset, despite the actual size of
|
||||
@@ -1236,6 +1238,26 @@ public class BinaryMessage extends BitSet
|
||||
return new String(bytes, UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse GB2312 Simplified Chinese Characters from the message starting at the offset.
|
||||
* @param offset where to start parsing.
|
||||
* @param characterCount number of characters to parse
|
||||
* @return parsed message.
|
||||
* @throws UnsupportedEncodingException if GB2312 encoding is not supported.
|
||||
*/
|
||||
public String parseGB2312(int offset, int characterCount) throws UnsupportedEncodingException
|
||||
{
|
||||
int length = characterCount * 2;
|
||||
byte[] bytes = new byte[length];
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||
}
|
||||
|
||||
return new String(bytes, GB2312);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse 16-bit unicode characters from the message starting at the offset.
|
||||
* @param offset where to start parsing.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 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,22 +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.controller.channel;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
@@ -41,12 +31,21 @@ import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
|
||||
public class ChannelAutoStartFrame extends JFrame
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelAutoStartFrame.class);
|
||||
|
||||
private static final int COUNTDOWN_SECONDS = 10;
|
||||
private Listener<ChannelEvent> mChannelEventListener;
|
||||
private List<Channel> mChannels;
|
||||
|
||||
@@ -55,7 +54,7 @@ public class ChannelAutoStartFrame extends JFrame
|
||||
private JButton mCancelButton;
|
||||
private JTable mChannelTable;
|
||||
private AtomicBoolean mChannelsStarted = new AtomicBoolean();
|
||||
|
||||
private int mAutoStartTimeoutSeconds;
|
||||
private ScheduledFuture<?> mTimerFuture;
|
||||
|
||||
/**
|
||||
@@ -66,10 +65,12 @@ public class ChannelAutoStartFrame extends JFrame
|
||||
* @param listener to receive channel start/enable request(s)
|
||||
* @param channels to auto-start
|
||||
*/
|
||||
public ChannelAutoStartFrame(Listener<ChannelEvent> listener, List<Channel> channels)
|
||||
public ChannelAutoStartFrame(Listener<ChannelEvent> listener, List<Channel> channels, UserPreferences userPreferences)
|
||||
{
|
||||
mChannelEventListener = listener;
|
||||
mChannels = channels;
|
||||
mAutoStartTimeoutSeconds = userPreferences.getApplicationPreference().getChannelAutoStartTimeout();
|
||||
|
||||
init();
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
@@ -100,7 +101,7 @@ public class ChannelAutoStartFrame extends JFrame
|
||||
"[][][grow,fill][]"));
|
||||
panel.add(new JLabel("The following channels will be automatically"), "span");
|
||||
|
||||
mCountdownLabel = new JLabel(getCountdownText(COUNTDOWN_SECONDS));
|
||||
mCountdownLabel = new JLabel(getCountdownText(mAutoStartTimeoutSeconds));
|
||||
panel.add(mCountdownLabel, "span");
|
||||
|
||||
panel.add(new JScrollPane(getChannelTable()), "span");
|
||||
@@ -220,7 +221,7 @@ public class ChannelAutoStartFrame extends JFrame
|
||||
*/
|
||||
public class CountdownTimer implements Runnable
|
||||
{
|
||||
private int mCount = COUNTDOWN_SECONDS;
|
||||
private int mCount = mAutoStartTimeoutSeconds;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
|
||||
@@ -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
|
||||
@@ -623,30 +623,27 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
Platform.runLater(() -> channel.setProcessing(false));
|
||||
}
|
||||
|
||||
//Since the processing chain's source will block on stop(), throw it to the thread pool to run
|
||||
ThreadPool.CACHED.submit(() -> {
|
||||
try
|
||||
{
|
||||
processingChain.stop();
|
||||
processingChain.removeEventLoggingModules();
|
||||
processingChain.removeRecordingModules();
|
||||
try
|
||||
{
|
||||
processingChain.stop();
|
||||
processingChain.removeEventLoggingModules();
|
||||
processingChain.removeRecordingModules();
|
||||
|
||||
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
||||
processingChain.removeFrequencyChangeListener(channel);
|
||||
channel.resetFrequencyCorrection();
|
||||
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
||||
processingChain.removeFrequencyChangeListener(channel);
|
||||
channel.resetFrequencyCorrection();
|
||||
|
||||
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
||||
mChannelEventBroadcaster.removeListener(processingChain);
|
||||
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
||||
mChannelEventBroadcaster.removeListener(processingChain);
|
||||
|
||||
//Unregister for event bus requests and notifications
|
||||
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
|
||||
processingChain.dispose();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error during shutdown of processing chain for channel [" + channel.getName() + "}", e);
|
||||
}
|
||||
});
|
||||
//Unregister for event bus requests and notifications
|
||||
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
|
||||
processingChain.dispose();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error during shutdown of processing chain for channel [" + channel.getName() + "}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -122,6 +122,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<In
|
||||
mChannels.add(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error adding polyphase channel source - " + (polyphaseChannelSource == null ? "source is null" :
|
||||
"channel source is already added to this channelizer"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,6 +141,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<In
|
||||
mChannels.remove(polyphaseChannelSource);
|
||||
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error removing polyphase channel source - " + (polyphaseChannelSource == null ? "source is null" :
|
||||
"channel source was not previously added to this channelizer"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,6 @@ import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.util.Dispatcher;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
@@ -69,8 +68,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
|
||||
|
||||
//Sized at 152 buffers a second where max = 5 seconds
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(5 * 152);
|
||||
//Sized to process 1/20th batch of 152 buffers a second, at 40 times per second
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(152 / 20, 25);
|
||||
private FloatFFT_1D mFFT;
|
||||
private float[] mInlineSamples;
|
||||
private float[] mInlineFilter;
|
||||
@@ -407,25 +406,32 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
||||
{
|
||||
public IFFTProcessorDispatcher(int maximumSize)
|
||||
public IFFTProcessorDispatcher(int batchSize, long interval)
|
||||
{
|
||||
super(maximumSize, "sdrtrunk polyphase ifft processor", Collections.emptyList());
|
||||
super("sdrtrunk polyphase ifft processor", batchSize, 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
|
||||
//channel results array contained in each results buffer and then dispatch the buffer
|
||||
//so that it can be distributed to each channel listener.
|
||||
setListener(list -> {
|
||||
List<float[]> processedChannelResults = new ArrayList<>();
|
||||
|
||||
for(float[] channelResults: list)
|
||||
try
|
||||
{
|
||||
//Rotate each of the channels to the correct phase using the IFFT
|
||||
mFFT.complexInverse(channelResults, true);
|
||||
processedChannelResults.add(channelResults);
|
||||
}
|
||||
List<float[]> processedChannelResults = new ArrayList<>();
|
||||
|
||||
dispatch(processedChannelResults);
|
||||
for(float[] channelResults: list)
|
||||
{
|
||||
//Rotate each of the channels to the correct phase using the IFFT
|
||||
mFFT.complexInverse(channelResults, true);
|
||||
processedChannelResults.add(channelResults);
|
||||
}
|
||||
|
||||
dispatch(processedChannelResults);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error during IFFT and dispatch of processed channel results", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.buffer.INativeBufferProvider;
|
||||
import io.github.dsheirer.buffer.NativeBufferPoisonPill;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
@@ -28,7 +27,6 @@ import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||
import io.github.dsheirer.source.Source;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.tuner.TunerController;
|
||||
@@ -44,7 +42,6 @@ import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -111,8 +108,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
|
||||
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||
mBufferDispatcher = new Dispatcher(500, "sdrtrunk polyphase buffer processor",
|
||||
new NativeBufferPoisonPill());
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 50, 10);
|
||||
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
||||
}
|
||||
|
||||
@@ -178,14 +174,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current channel sample rate which is (2 * channel bandwidth).
|
||||
*/
|
||||
public double getChannelSampleRate()
|
||||
{
|
||||
return mChannelCalculator.getChannelSampleRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Current channel bandwidth/spacing.
|
||||
*/
|
||||
@@ -440,9 +428,10 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
break;
|
||||
case REQUEST_STOP_SAMPLE_STREAM:
|
||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
|
||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource channelSource)
|
||||
{
|
||||
stopChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
|
||||
stopChannelSource(channelSource);
|
||||
channelSource.dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -450,14 +439,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
||||
}
|
||||
break;
|
||||
case REQUEST_SOURCE_DISPOSE:
|
||||
Source source = sourceEvent.getSource();
|
||||
|
||||
if(source instanceof PolyphaseChannelSource)
|
||||
{
|
||||
source.dispose();
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||
//Rebroadcast so that the tuner source can process this event
|
||||
mSourceEventBroadcaster.broadcast(sourceEvent);
|
||||
@@ -481,7 +462,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
public class NativeBufferReceiver implements Listener<INativeBuffer>
|
||||
{
|
||||
private boolean mOutputProcessorUpdateRequired = false;
|
||||
private ReentrantLock mUpdateFlagLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Processes tuner center frequency change source events to flag when output processors need updating.
|
||||
@@ -493,18 +473,9 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
|
||||
if(mChannelCalculator.getCenterFrequency() != frequency)
|
||||
{
|
||||
mUpdateFlagLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
//Update the channel calculator frequency so that it's ready when the output processor update occurs
|
||||
mChannelCalculator.setCenterFrequency(frequency);
|
||||
mOutputProcessorUpdateRequired = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
mUpdateFlagLock.unlock();
|
||||
}
|
||||
//Update the channel calculator frequency so that it's ready when the output processor update occurs
|
||||
mChannelCalculator.setCenterFrequency(frequency);
|
||||
mOutputProcessorUpdateRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,19 +487,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
@Override
|
||||
public void receive(INativeBuffer nativeBuffer)
|
||||
{
|
||||
mUpdateFlagLock.lock();
|
||||
|
||||
try
|
||||
if(mOutputProcessorUpdateRequired)
|
||||
{
|
||||
if(mOutputProcessorUpdateRequired)
|
||||
try
|
||||
{
|
||||
updateOutputProcessors();
|
||||
mOutputProcessorUpdateRequired = false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mUpdateFlagLock.unlock();
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error updating polyphase channel output processors");
|
||||
}
|
||||
mOutputProcessorUpdateRequired = false;
|
||||
}
|
||||
|
||||
if(mPolyphaseChannelizer != null)
|
||||
|
||||
@@ -25,12 +25,10 @@ import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -43,15 +41,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
||||
private Listener<ComplexSamples> mSamplesListener;
|
||||
private double mChannelSampleRate;
|
||||
private long mIndexCenterFrequency;
|
||||
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
|
||||
|
||||
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
|
||||
private double mTunerSampleRate;
|
||||
private double mTunerCenterFrequency;
|
||||
|
||||
private PendingOutputProcessorUpdate mPendingOutputProcessorUpdate;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -67,8 +63,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
super(producerSourceEventListener, tunerChannel);
|
||||
mChannelSampleRate = channelCalculator.getChannelSampleRate();
|
||||
mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS);
|
||||
updateOutputProcessor(channelCalculator, filterManager);
|
||||
doUpdateOutputProcessor(channelCalculator, filterManager);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,8 +106,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
super.start();
|
||||
|
||||
mStreamHeartbeatProcessor.start();
|
||||
|
||||
if(mPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
@@ -128,8 +121,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.stop();
|
||||
}
|
||||
|
||||
mStreamHeartbeatProcessor.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,112 +129,129 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
@Override
|
||||
public void setListener(final Listener<ComplexSamples> listener)
|
||||
{
|
||||
mStreamHeartbeatProcessor.setListener(listener);
|
||||
mSamplesListener = listener;
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexSamples complexSamples)
|
||||
{
|
||||
mStreamHeartbeatProcessor.receive(complexSamples);
|
||||
if(mSamplesListener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mSamplesListener.receive(complexSamples);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error dispatching complex samples to listener [" + mSamplesListener.getClass() + "]", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the output processor whenever the source tuner's center frequency changes.
|
||||
* Queues a request to update the output processor whenever the source tuner's center frequency changes.
|
||||
* @param channelCalculator providing access to updated tuner center frequency, sample rate, etc.
|
||||
* @param filterManager for designing and caching synthesis filters
|
||||
*/
|
||||
public void updateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager) throws IllegalArgumentException
|
||||
public void updateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
mPendingOutputProcessorUpdate = new PendingOutputProcessorUpdate(channelCalculator, filterManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the output processor update. This method must be invoked on the channel results calling thread to
|
||||
* ensure we don't have thread contention on the output processor.
|
||||
* @param channelCalculator for updates
|
||||
* @param filterManager for updates
|
||||
* @throws IllegalArgumentException if there's an issue.
|
||||
*/
|
||||
public void doUpdateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
String errorMessage = null;
|
||||
|
||||
mOutputProcessorLock.lock();
|
||||
//If a change in sample rate or center frequency makes this channel no longer viable, then the channel
|
||||
//calculator will throw an IllegalArgException ... handled below
|
||||
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
|
||||
|
||||
try
|
||||
mOutputProcessorIndexes.clear();
|
||||
mOutputProcessorIndexes.addAll(indexes);
|
||||
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
|
||||
mTunerSampleRate = channelCalculator.getSampleRate();
|
||||
|
||||
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
|
||||
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
|
||||
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
|
||||
|
||||
if(mPolyphaseChannelOutputProcessor != null && mPolyphaseChannelOutputProcessor.getInputChannelCount() == indexes.size())
|
||||
{
|
||||
//If a change in sample rate or center frequency makes this channel no longer viable, then the channel
|
||||
//calculator will throw an IllegalArgException ... handled below
|
||||
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
|
||||
mPolyphaseChannelOutputProcessor.setPolyphaseChannelIndices(indexes);
|
||||
|
||||
mOutputProcessorIndexes.clear();
|
||||
mOutputProcessorIndexes.addAll(indexes);
|
||||
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
|
||||
mTunerSampleRate = channelCalculator.getSampleRate();
|
||||
|
||||
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
|
||||
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
|
||||
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
|
||||
|
||||
if(mPolyphaseChannelOutputProcessor != null && mPolyphaseChannelOutputProcessor.getInputChannelCount() == indexes.size())
|
||||
if(indexes.size() > 1)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.setPolyphaseChannelIndices(indexes);
|
||||
|
||||
if(indexes.size() > 1)
|
||||
try
|
||||
{
|
||||
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
|
||||
channelCalculator.getChannelBandwidth(), indexes.size());
|
||||
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Error creating an updated synthesis filter for the channel output processor");
|
||||
errorMessage ="Cannot update output processor - unable to design synthesis filter for [" +
|
||||
indexes.size() + "] channel indices - channel sample rate [" + channelCalculator.getChannelSampleRate() +
|
||||
"] channel bandwidth [" + channelCalculator.getChannelBandwidth() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
}
|
||||
else //Create a new output processor.
|
||||
{
|
||||
if(mPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.setListener(null);
|
||||
mPolyphaseChannelOutputProcessor.stop();
|
||||
}
|
||||
|
||||
mPolyphaseChannelOutputProcessor = null;
|
||||
|
||||
switch(indexes.size())
|
||||
{
|
||||
case 1:
|
||||
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
||||
indexes, channelCalculator.getChannelCount(), getHeartbeatManager());
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
break;
|
||||
case 2:
|
||||
try
|
||||
{
|
||||
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
|
||||
channelCalculator.getChannelBandwidth(), indexes.size());
|
||||
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Error creating an updated synthesis filter for the channel output processor");
|
||||
errorMessage ="Cannot update output processor - unable to design synthesis filter for [" +
|
||||
indexes.size() + "] channel indices";
|
||||
}
|
||||
}
|
||||
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
}
|
||||
else //Create a new output processor.
|
||||
{
|
||||
if(mPolyphaseChannelOutputProcessor != null)
|
||||
{
|
||||
mPolyphaseChannelOutputProcessor.setListener(null);
|
||||
}
|
||||
|
||||
mPolyphaseChannelOutputProcessor = null;
|
||||
|
||||
switch(indexes.size())
|
||||
{
|
||||
case 1:
|
||||
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
||||
indexes, channelCalculator.getChannelCount());
|
||||
channelCalculator.getChannelBandwidth(), 2);
|
||||
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
||||
indexes, filter, channelCalculator.getChannelCount(), getHeartbeatManager());
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
break;
|
||||
case 2:
|
||||
try
|
||||
{
|
||||
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
|
||||
channelCalculator.getChannelBandwidth(), 2);
|
||||
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
||||
indexes, filter, channelCalculator.getChannelCount());
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||
mPolyphaseChannelOutputProcessor.start();
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
errorMessage = "Cannot create new output processor - unable to design synthesis filter for [" +
|
||||
indexes.size() + "] channel indices";
|
||||
mLog.error("Error creating a synthesis filter for a new channel output processor");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
|
||||
mLog.info(channelCalculator.toString());
|
||||
errorMessage = "Unable to create new channel output processor - unexpected channel index size: " +
|
||||
indexes.size();
|
||||
}
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
errorMessage = "Cannot create new output processor - unable to design synthesis filter for [" +
|
||||
indexes.size() + "] channel indices";
|
||||
mLog.error("Error creating a synthesis filter for a new channel output processor");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
|
||||
mLog.info(channelCalculator.toString());
|
||||
errorMessage = "Unable to create new channel output processor - unexpected channel index size: " +
|
||||
indexes.size();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mOutputProcessorLock.unlock();
|
||||
}
|
||||
|
||||
//Unlikely, but if we had an error designing a synthesis filter, throw an exception
|
||||
if(errorMessage != null)
|
||||
@@ -262,7 +270,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
*/
|
||||
public void receiveChannelResults(List<float[]> channelResultsList, long currentSamplesTimestamp)
|
||||
{
|
||||
mOutputProcessorLock.lock();
|
||||
if(mPendingOutputProcessorUpdate != null)
|
||||
{
|
||||
ChannelCalculator channelCalculator = mPendingOutputProcessorUpdate.getChannelCalculator();
|
||||
SynthesisFilterManager filterManager = mPendingOutputProcessorUpdate.getSynthesisFilterManager();
|
||||
mPendingOutputProcessorUpdate = null;
|
||||
doUpdateOutputProcessor(channelCalculator, filterManager);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -271,9 +285,9 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList, currentSamplesTimestamp);
|
||||
}
|
||||
}
|
||||
finally
|
||||
catch(Exception e)
|
||||
{
|
||||
mOutputProcessorLock.unlock();
|
||||
mLog.error("Error processing channel results", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,4 +356,40 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
|
||||
getTunerChannel().getFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Support classes needed to update the output processor.
|
||||
*/
|
||||
public class PendingOutputProcessorUpdate
|
||||
{
|
||||
private final ChannelCalculator mChannelCalculator;
|
||||
private final SynthesisFilterManager mSynthesisFilterManager;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channelCalculator for support
|
||||
* @param synthesisFilterManager for support
|
||||
*/
|
||||
public PendingOutputProcessorUpdate(ChannelCalculator channelCalculator, SynthesisFilterManager synthesisFilterManager)
|
||||
{
|
||||
mChannelCalculator = channelCalculator;
|
||||
mSynthesisFilterManager = synthesisFilterManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel calculator
|
||||
*/
|
||||
public ChannelCalculator getChannelCalculator()
|
||||
{
|
||||
return mChannelCalculator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel calculator
|
||||
*/
|
||||
public SynthesisFilterManager getSynthesisFilterManager()
|
||||
{
|
||||
return mSynthesisFilterManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 +20,8 @@ package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||
import io.github.dsheirer.util.Dispatcher;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -31,6 +31,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
|
||||
|
||||
private Dispatcher<List<float[]>> mChannelResultsDispatcher;
|
||||
private HeartbeatManager mHeartbeatManager;
|
||||
protected Listener<ComplexSamples> mComplexSamplesListener;
|
||||
private int mInputChannelCount;
|
||||
private long mCurrentSampleTimestamp = System.currentTimeMillis();
|
||||
@@ -42,12 +43,25 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
* @param inputChannelCount is the number of input channels for this output processor
|
||||
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
|
||||
* channel sample rate for frequency translation/correction.
|
||||
* @param heartbeatManager to receive pings on the dispatcher thread
|
||||
*/
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
|
||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, HeartbeatManager heartbeatManager)
|
||||
{
|
||||
mInputChannelCount = inputChannelCount;
|
||||
mChannelResultsDispatcher = new Dispatcher<>((int)sampleRate, "sdrtrunk polyphase channel", Collections.emptyList());
|
||||
mChannelResultsDispatcher.setListener(floats -> process(floats));
|
||||
//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.setListener(floats -> {
|
||||
try
|
||||
{
|
||||
process(floats);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error processing channel results", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -36,10 +37,12 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing a single channel index.
|
||||
* @param gain value to apply. This is typically the same as the channelizer's channel count.
|
||||
* @param heartbeatManager to receive heartbeats on the dispatch thread
|
||||
*/
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain)
|
||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain,
|
||||
HeartbeatManager heartbeatManager)
|
||||
{
|
||||
super(1, sampleRate);
|
||||
super(1, sampleRate, heartbeatManager);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
mMixerAssembler = new OneChannelMixerAssembler(gain);
|
||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -37,12 +38,14 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
||||
* @param sampleRate of the output sample stream.
|
||||
* @param channelIndexes containing two channel indices.
|
||||
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
|
||||
* @param heartbeatManager to be pinged on the dispatcher thread
|
||||
*/
|
||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain)
|
||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain,
|
||||
HeartbeatManager heartbeatManager)
|
||||
{
|
||||
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
|
||||
//after synthesizing both input channels
|
||||
super(2, sampleRate);
|
||||
super(2, sampleRate, heartbeatManager);
|
||||
setPolyphaseChannelIndices(channelIndexes);
|
||||
mMixerAssembler = new TwoChannelMixerAssembler(gain);
|
||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||
|
||||
@@ -313,7 +313,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
}
|
||||
else
|
||||
{
|
||||
new ChannelAutoStartFrame(mPlaylistManager.getChannelProcessingManager(), channels);
|
||||
new ChannelAutoStartFrame(mPlaylistManager.getChannelProcessingManager(), channels, mUserPreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||
import io.github.dsheirer.record.binary.BinaryReader;
|
||||
import java.io.File;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.prefs.Preferences;
|
||||
import javafx.application.Application;
|
||||
@@ -35,6 +37,7 @@ 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.Scene;
|
||||
@@ -42,9 +45,15 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
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;
|
||||
@@ -60,6 +69,7 @@ import org.slf4j.LoggerFactory;
|
||||
public class DMRRecordingViewer extends VBox
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DMRRecordingViewer.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;
|
||||
@@ -243,7 +253,56 @@ public class DMRRecordingViewer extends VBox
|
||||
{
|
||||
if(mMessageTableView == null)
|
||||
{
|
||||
mMessageTableView = new TableView<>(mFilteredMessages);
|
||||
mMessageTableView = new TableView<>();
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package io.github.dsheirer.gui.preference;
|
||||
|
||||
import io.github.dsheirer.gui.preference.application.ApplicationPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
|
||||
@@ -39,6 +40,8 @@ public class PreferenceEditorFactory
|
||||
{
|
||||
switch(preferenceEditorType)
|
||||
{
|
||||
case APPLICATION:
|
||||
return new ApplicationPreferenceEditor(userPreferences);
|
||||
case AUDIO_DUPLICATE_CALL_DETECTION:
|
||||
return new DuplicateCallPreferenceEditor(userPreferences);
|
||||
case AUDIO_MP3:
|
||||
|
||||
@@ -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,6 +24,7 @@ package io.github.dsheirer.gui.preference;
|
||||
*/
|
||||
public enum PreferenceEditorType
|
||||
{
|
||||
APPLICATION("Application"),
|
||||
CHANNEL_EVENT("Channel Events"),
|
||||
DIRECTORY("Directories"),
|
||||
JMBE_LIBRARY("JMBE Audio Library"),
|
||||
|
||||
@@ -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
|
||||
@@ -165,6 +165,11 @@ public class UserPreferencesEditor extends BorderPane
|
||||
{
|
||||
TreeItem<String> treeRoot = new TreeItem<>("Root node");
|
||||
|
||||
TreeItem<String> applicationItem = new TreeItem<>("Application");
|
||||
applicationItem.getChildren().add(new TreeItem(PreferenceEditorType.APPLICATION));
|
||||
treeRoot.getChildren().add(applicationItem);
|
||||
applicationItem.setExpanded(true);
|
||||
|
||||
TreeItem<String> audioItem = new TreeItem<>("Audio");
|
||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_DUPLICATE_CALL_DETECTION));
|
||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_MP3));
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.preference.application;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.application.ApplicationPreference;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
|
||||
/**
|
||||
* Preference settings for application
|
||||
*/
|
||||
public class ApplicationPreferenceEditor extends HBox
|
||||
{
|
||||
private ApplicationPreference mApplicationPreference;
|
||||
private GridPane mEditorPane;
|
||||
private Label mAutoStartTimeoutLabel;
|
||||
private Spinner<Integer> mTimeoutSpinner;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for obtaining reference to preference.
|
||||
*/
|
||||
public ApplicationPreferenceEditor(UserPreferences userPreferences)
|
||||
{
|
||||
mApplicationPreference = userPreferences.getApplicationPreference();
|
||||
getChildren().add(getEditorPane());
|
||||
}
|
||||
|
||||
private GridPane getEditorPane()
|
||||
{
|
||||
if(mEditorPane == null)
|
||||
{
|
||||
int row = 0;
|
||||
mEditorPane = new GridPane();
|
||||
mEditorPane.setVgap(10);
|
||||
mEditorPane.setHgap(10);
|
||||
mEditorPane.setPadding(new Insets(10, 10, 10, 10));
|
||||
GridPane.setHalignment(getAutoStartTimeoutLabel(), HPos.RIGHT);
|
||||
mEditorPane.add(getAutoStartTimeoutLabel(), 0, row);
|
||||
mEditorPane.add(getTimeoutSpinner(), 1, row);
|
||||
mEditorPane.add(new Label("seconds"), 2, row);
|
||||
}
|
||||
|
||||
return mEditorPane;
|
||||
}
|
||||
|
||||
private Label getAutoStartTimeoutLabel()
|
||||
{
|
||||
if(mAutoStartTimeoutLabel == null)
|
||||
{
|
||||
mAutoStartTimeoutLabel = new Label("Channel Auto-Start Timeout");
|
||||
}
|
||||
|
||||
return mAutoStartTimeoutLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spinner to select channel auto-start timeout value in range 0-30 seconds.
|
||||
* @return spinner
|
||||
*/
|
||||
private Spinner<Integer> getTimeoutSpinner()
|
||||
{
|
||||
if(mTimeoutSpinner == null)
|
||||
{
|
||||
mTimeoutSpinner = new Spinner<>(0, 30, mApplicationPreference.getChannelAutoStartTimeout(), 1);
|
||||
mTimeoutSpinner.valueProperty().addListener((observable, oldValue, newValue) -> mApplicationPreference.setChannelAutoStartTimeout(newValue));
|
||||
}
|
||||
|
||||
return mTimeoutSpinner;
|
||||
}
|
||||
}
|
||||
@@ -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,10 +24,9 @@ import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.playback.PlaybackPreference;
|
||||
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
||||
import io.github.dsheirer.source.mixer.MixerManager;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
@@ -49,9 +48,6 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Clip;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
|
||||
/**
|
||||
@@ -60,20 +56,16 @@ import java.nio.ShortBuffer;
|
||||
public class PlaybackPreferenceEditor extends HBox
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(PlaybackPreferenceEditor.class);
|
||||
private PlaybackPreference mPlaybackPreference;
|
||||
private final PlaybackPreference mPlaybackPreference;
|
||||
private GridPane mEditorPane;
|
||||
private ComboBox<MixerChannelConfiguration> mMixerComboBox;
|
||||
private Button mMixerTestButton;
|
||||
private ToggleSwitch mUseAudioSegmentStartToneSwitch;
|
||||
private Button mTestStartToneButton;
|
||||
private ToggleSwitch mUseAudioSegmentPreemptToneSwitch;
|
||||
private Button mTestPreemptToneButton;
|
||||
private ToggleSwitch mUseAudioSegmentDropToneSwitch;
|
||||
private Button mTestDropToneButton;
|
||||
private ComboBox<ToneFrequency> mStartToneFrequencyComboBox;
|
||||
private ComboBox<ToneVolume> mStartToneVolumeComboBox;
|
||||
private ComboBox<ToneFrequency> mPreemptToneFrequencyComboBox;
|
||||
private ComboBox<ToneVolume> mPreemptToneVolumeComboBox;
|
||||
private ComboBox<ToneFrequency> mDropToneFrequencyComboBox;
|
||||
private ComboBox<ToneVolume> mDropToneVolumeComboBox;
|
||||
|
||||
@@ -114,18 +106,6 @@ public class PlaybackPreferenceEditor extends HBox
|
||||
mEditorPane.add(getStartToneVolumeComboBox(), 4, row);
|
||||
mEditorPane.add(getTestStartToneButton(), 5, row);
|
||||
|
||||
mEditorPane.add(getUseAudioSegmentPreemptToneSwitch(), 0, ++row);
|
||||
mEditorPane.add(new Label("Priority Preempt Tone"), 1, row, 3, 1);
|
||||
Label preemptFrequencyLabel = new Label("Frequency:");
|
||||
GridPane.setHalignment(preemptFrequencyLabel, HPos.RIGHT);
|
||||
mEditorPane.add(preemptFrequencyLabel, 1, ++row);
|
||||
mEditorPane.add(getPreemptToneFrequencyComboBox(), 2, row);
|
||||
Label preemptVolumeLabel = new Label("Volume:");
|
||||
GridPane.setHalignment(preemptVolumeLabel, HPos.RIGHT);
|
||||
mEditorPane.add(preemptVolumeLabel, 3, row);
|
||||
mEditorPane.add(getPreemptToneVolumeComboBox(), 4, row);
|
||||
mEditorPane.add(getTestPreemptToneButton(), 5, row);
|
||||
|
||||
mEditorPane.add(getUseAudioSegmentDropToneSwitch(), 0, ++row);
|
||||
mEditorPane.add(new Label("Drop Tone - Do Not Monitor"), 1, row, 3, 1);
|
||||
Label dropFrequencyLabel = new Label("Frequency:");
|
||||
@@ -177,52 +157,21 @@ public class PlaybackPreferenceEditor extends HBox
|
||||
mUseAudioSegmentStartToneSwitch = new ToggleSwitch();
|
||||
mUseAudioSegmentStartToneSwitch.setAlignment(Pos.BASELINE_RIGHT);
|
||||
mUseAudioSegmentStartToneSwitch.setSelected(mPlaybackPreference.getUseAudioSegmentStartTone());
|
||||
mUseAudioSegmentStartToneSwitch.selectedProperty().addListener(new ChangeListener<Boolean>()
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
|
||||
{
|
||||
mPlaybackPreference.setUseAudioSegmentStartTone(newValue);
|
||||
}
|
||||
});
|
||||
mUseAudioSegmentStartToneSwitch.selectedProperty().addListener((observable, oldValue, newValue) ->
|
||||
mPlaybackPreference.setUseAudioSegmentStartTone(newValue));
|
||||
}
|
||||
|
||||
return mUseAudioSegmentStartToneSwitch;
|
||||
}
|
||||
|
||||
private ToggleSwitch getUseAudioSegmentPreemptToneSwitch()
|
||||
{
|
||||
if(mUseAudioSegmentPreemptToneSwitch == null)
|
||||
{
|
||||
mUseAudioSegmentPreemptToneSwitch = new ToggleSwitch();
|
||||
mUseAudioSegmentPreemptToneSwitch.setSelected(mPlaybackPreference.getUseAudioSegmentPreemptTone());
|
||||
mUseAudioSegmentPreemptToneSwitch.selectedProperty().addListener(new ChangeListener<Boolean>()
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
|
||||
{
|
||||
mPlaybackPreference.setUseAudioSegmentPreemptTone(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mUseAudioSegmentPreemptToneSwitch;
|
||||
}
|
||||
|
||||
private ToggleSwitch getUseAudioSegmentDropToneSwitch()
|
||||
{
|
||||
if(mUseAudioSegmentDropToneSwitch == null)
|
||||
{
|
||||
mUseAudioSegmentDropToneSwitch = new ToggleSwitch();
|
||||
mUseAudioSegmentDropToneSwitch.setSelected(mPlaybackPreference.getUseAudioSegmentDropTone());
|
||||
mUseAudioSegmentDropToneSwitch.selectedProperty().addListener(new ChangeListener<Boolean>()
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
|
||||
{
|
||||
mPlaybackPreference.setUseAudioSegmentDropTone(newValue);
|
||||
}
|
||||
});
|
||||
mUseAudioSegmentDropToneSwitch.selectedProperty().addListener((observable, oldValue, newValue) ->
|
||||
mPlaybackPreference.setUseAudioSegmentDropTone(newValue));
|
||||
}
|
||||
|
||||
return mUseAudioSegmentDropToneSwitch;
|
||||
@@ -236,14 +185,7 @@ public class PlaybackPreferenceEditor extends HBox
|
||||
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
||||
iconNode.setFill(Color.CORNFLOWERBLUE);
|
||||
mTestStartToneButton.setGraphic(iconNode);
|
||||
mTestStartToneButton.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
play(mPlaybackPreference.getStartTone());
|
||||
}
|
||||
});
|
||||
mTestStartToneButton.setOnAction(event -> play(mPlaybackPreference.getStartTone()));
|
||||
|
||||
mTestStartToneButton.disableProperty().bind(getUseAudioSegmentStartToneSwitch().selectedProperty().not());
|
||||
}
|
||||
@@ -251,29 +193,6 @@ public class PlaybackPreferenceEditor extends HBox
|
||||
return mTestStartToneButton;
|
||||
}
|
||||
|
||||
public Button getTestPreemptToneButton()
|
||||
{
|
||||
if(mTestPreemptToneButton == null)
|
||||
{
|
||||
mTestPreemptToneButton = new Button("Test");
|
||||
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
||||
iconNode.setFill(Color.CORNFLOWERBLUE);
|
||||
mTestPreemptToneButton.setGraphic(iconNode);
|
||||
mTestPreemptToneButton.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
play(mPlaybackPreference.getPreemptTone());
|
||||
}
|
||||
});
|
||||
|
||||
mTestPreemptToneButton.disableProperty().bind(getUseAudioSegmentPreemptToneSwitch().selectedProperty().not());
|
||||
}
|
||||
|
||||
return mTestPreemptToneButton;
|
||||
}
|
||||
|
||||
public Button getTestDropToneButton()
|
||||
{
|
||||
if(mTestDropToneButton == null)
|
||||
@@ -282,14 +201,7 @@ public class PlaybackPreferenceEditor extends HBox
|
||||
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
||||
iconNode.setFill(Color.CORNFLOWERBLUE);
|
||||
mTestDropToneButton.setGraphic(iconNode);
|
||||
mTestDropToneButton.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
play(mPlaybackPreference.getDropTone());
|
||||
}
|
||||
});
|
||||
mTestDropToneButton.setOnAction(event -> play(mPlaybackPreference.getDropTone()));
|
||||
|
||||
mTestDropToneButton.disableProperty().bind(getUseAudioSegmentDropToneSwitch().selectedProperty().not());
|
||||
}
|
||||
@@ -357,36 +269,6 @@ public class PlaybackPreferenceEditor extends HBox
|
||||
return mStartToneVolumeComboBox;
|
||||
}
|
||||
|
||||
public ComboBox<ToneFrequency> getPreemptToneFrequencyComboBox()
|
||||
{
|
||||
if(mPreemptToneFrequencyComboBox == null)
|
||||
{
|
||||
mPreemptToneFrequencyComboBox = new ComboBox<>();
|
||||
mPreemptToneFrequencyComboBox.getItems().addAll(ToneFrequency.values());
|
||||
mPreemptToneFrequencyComboBox.getSelectionModel().select(mPlaybackPreference.getPreemptToneFrequency());
|
||||
mPreemptToneFrequencyComboBox.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> mPlaybackPreference.setPreemptToneFrequency(newValue));
|
||||
mPreemptToneFrequencyComboBox.disableProperty().bind(getUseAudioSegmentPreemptToneSwitch().selectedProperty().not());
|
||||
}
|
||||
|
||||
return mPreemptToneFrequencyComboBox;
|
||||
}
|
||||
|
||||
public ComboBox<ToneVolume> getPreemptToneVolumeComboBox()
|
||||
{
|
||||
if(mPreemptToneVolumeComboBox == null)
|
||||
{
|
||||
mPreemptToneVolumeComboBox = new ComboBox<>();
|
||||
mPreemptToneVolumeComboBox.getItems().addAll(ToneVolume.values());
|
||||
mPreemptToneVolumeComboBox.getSelectionModel().select(mPlaybackPreference.getPreemptToneVolume());
|
||||
mPreemptToneVolumeComboBox.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> mPlaybackPreference.setPreemptToneVolume(newValue));
|
||||
mPreemptToneVolumeComboBox.disableProperty().bind(getUseAudioSegmentPreemptToneSwitch().selectedProperty().not());
|
||||
}
|
||||
|
||||
return mPreemptToneVolumeComboBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the audio buffer over the default mono playback device
|
||||
* @param audioSamples with 8 kHz mono PCM samples
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.github.dsheirer.channel.state.TimeslotDecoderState;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
import io.github.dsheirer.controller.channel.ChannelConfigurationChangeNotification;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
@@ -74,6 +75,8 @@ import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||
import io.github.dsheirer.util.PacketUtil;
|
||||
@@ -299,27 +302,60 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||
|
||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||
.eventDescription(DecodeEventType.DATA_PACKET.name())
|
||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||
.timeslot(packet.getTimeslot())
|
||||
.details(packet.toString())
|
||||
.build();
|
||||
//Hytera SDS Long SMS message
|
||||
if(packet.getPacket() instanceof HyteraSmsPacket hyteraSmsPacket)
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
mic.remove(Form.RADIO);
|
||||
mic.remove(Form.TALKGROUP);
|
||||
mic.update(hyteraSmsPacket.getSource());
|
||||
mic.update(hyteraSmsPacket.getDestination());
|
||||
|
||||
broadcast(packetEvent);
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||
.eventDescription(DecodeEventType.SMS.name())
|
||||
.identifiers(mic)
|
||||
.timeslot(packet.getTimeslot())
|
||||
.details("SMS:" + hyteraSmsPacket.getSMS())
|
||||
.build();
|
||||
broadcast(smsEvent);
|
||||
}
|
||||
//Unknown Hytera Long Data Service Token Message
|
||||
else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket)
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
|
||||
|
||||
if (geoPosition != null) {
|
||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.eventDescription(DecodeEventType.GPS.toString())
|
||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||
.eventDescription(DecodeEventType.UNKNOWN_PACKET.name())
|
||||
.identifiers(mic)
|
||||
.timeslot(packet.getTimeslot())
|
||||
.details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||
.build();
|
||||
broadcast(unknownTokenEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||
.eventDescription(DecodeEventType.DATA_PACKET.name())
|
||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||
.protocol(Protocol.LRRP)
|
||||
.location(geoPosition)
|
||||
.timeslot(packet.getTimeslot())
|
||||
.details(packet.toString())
|
||||
.build();
|
||||
|
||||
broadcast(plottableDecodeEvent);
|
||||
broadcast(packetEvent);
|
||||
|
||||
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
|
||||
|
||||
if (geoPosition != null) {
|
||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.eventDescription(DecodeEventType.GPS.toString())
|
||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||
.protocol(Protocol.LRRP)
|
||||
.location(geoPosition)
|
||||
.build();
|
||||
|
||||
broadcast(plottableDecodeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,7 +664,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
break;
|
||||
case HYTERA_08_ANNOUNCEMENT:
|
||||
case HYTERA_68_ANNOUNCEMENT:
|
||||
case HYTERA_XPT_SITE_STATE:
|
||||
case HYTERA_68_XPT_SITE_STATE:
|
||||
|
||||
case MOTOROLA_CAPPLUS_NEIGHBOR_REPORT:
|
||||
if(csbk instanceof CapacityPlusNeighbors)
|
||||
|
||||
@@ -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
|
||||
@@ -30,17 +30,14 @@ import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
||||
import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.lrrp.token.Identity;
|
||||
import io.github.dsheirer.module.decode.ip.lrrp.token.Token;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Identity;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Token;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import io.github.dsheirer.record.binary.BinaryReader;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.DirectoryStream;
|
||||
@@ -53,6 +50,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DMR Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.dmr.identifier;
|
||||
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
|
||||
|
||||
/**
|
||||
* DMR Tier-III (ie trunked) radio identifier with custom overrides to correctly label Gateways & Identities
|
||||
*/
|
||||
public class DmrTier3Radio extends DMRRadio
|
||||
{
|
||||
public DmrTier3Radio(Integer value, Role role)
|
||||
{
|
||||
super(value, role);
|
||||
}
|
||||
|
||||
public boolean isGateway()
|
||||
{
|
||||
return Tier3Gateway.isGateway(getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(isGateway())
|
||||
{
|
||||
return Tier3Gateway.fromValue(getValue()).getLabel();
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR TO radio identifier
|
||||
*/
|
||||
public static RadioIdentifier createTo(int radioId)
|
||||
{
|
||||
return new DmrTier3Radio(radioId, Role.TO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR FROM radio identifier
|
||||
*/
|
||||
public static RadioIdentifier createFrom(int radioId)
|
||||
{
|
||||
return new DmrTier3Radio(radioId, Role.FROM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR ANY radio identifier
|
||||
*/
|
||||
public static RadioIdentifier createAny(int radioId)
|
||||
{
|
||||
return new DmrTier3Radio(radioId, Role.ANY);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -34,6 +33,8 @@ import java.util.List;
|
||||
*/
|
||||
public abstract class DataBlock extends DataMessage
|
||||
{
|
||||
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
@@ -63,7 +64,11 @@ public abstract class DataBlock extends DataMessage
|
||||
/**
|
||||
* Serial number for a confirmed data block
|
||||
*/
|
||||
public abstract int getDataBlockSerialNumber();
|
||||
public int getDataBlockSerialNumber()
|
||||
{
|
||||
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
|
||||
@@ -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
|
||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
*/
|
||||
public class DataBlock1Rate extends DataBlock
|
||||
{
|
||||
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
private static final int CONFIRMED_PAYLOAD_START = 16;
|
||||
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
||||
@@ -56,17 +55,16 @@ public class DataBlock1Rate extends DataBlock
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("RATE 1/1 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
}
|
||||
sb.append(" RATE 1/1 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataBlockSerialNumber()
|
||||
{
|
||||
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorrectedBinaryMessage getConfirmedPayload()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
*/
|
||||
public class DataBlock1_2Rate extends DataBlock
|
||||
{
|
||||
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
private static final int CONFIRMED_PAYLOAD_START = 16;
|
||||
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
||||
@@ -56,17 +55,16 @@ public class DataBlock1_2Rate extends DataBlock
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("RATE 1/2 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
}
|
||||
sb.append(" RATE 1/2 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataBlockSerialNumber()
|
||||
{
|
||||
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorrectedBinaryMessage getConfirmedPayload()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
*/
|
||||
public class DataBlock3_4Rate extends DataBlock
|
||||
{
|
||||
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
private static final int CONFIRMED_PAYLOAD_START = 16;
|
||||
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
||||
@@ -57,17 +56,16 @@ public class DataBlock3_4Rate extends DataBlock
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("RATE 3/4 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
}
|
||||
sb.append(" RATE 3/4 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDataBlockSerialNumber()
|
||||
{
|
||||
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorrectedBinaryMessage getConfirmedPayload()
|
||||
{
|
||||
|
||||
@@ -23,13 +23,14 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.Hytera08Acknowledge;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.Hytera68Acknowledge;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAdjacentSiteInformation;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAloha;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko32;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko44;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko47;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraSmsWaitingNotification;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraSmsAvailableNotification;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
||||
@@ -221,7 +222,7 @@ public class CSBKMessageFactory
|
||||
csbk = new Preamble(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
|
||||
case HYTERA_ALOHA:
|
||||
case HYTERA_68_ALOHA:
|
||||
csbk = new HyteraAloha(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case HYTERA_08_ANNOUNCEMENT:
|
||||
@@ -237,23 +238,26 @@ public class CSBKMessageFactory
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case HYTERA_XPT_PREAMBLE:
|
||||
case HYTERA_68_XPT_PREAMBLE:
|
||||
csbk = new HyteraXPTPreamble(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case HYTERA_XPT_SITE_STATE:
|
||||
case HYTERA_68_XPT_SITE_STATE:
|
||||
csbk = new HyteraXPTSiteState(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case HYTERA_CSBKO_32:
|
||||
csbk = new HyteraCsbko32(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
case HYTERA_08_ACKNOWLEDGE:
|
||||
csbk = new Hytera08Acknowledge(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case HYTERA_CSBKO_44:
|
||||
case HYTERA_08_CSBKO_44:
|
||||
csbk = new HyteraCsbko44(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case HYTERA_CSBKO_47:
|
||||
case HYTERA_08_CSBKO_47:
|
||||
csbk = new HyteraCsbko47(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case HYTERA_CSBKO_62:
|
||||
csbk = new HyteraSmsWaitingNotification(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
case HYTERA_68_ACKNOWLEDGE:
|
||||
csbk = new Hytera68Acknowledge(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case HYTERA_68_CSBKO_62:
|
||||
csbk = new HyteraSmsAvailableNotification(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case MOTOROLA_CAPMAX_ALOHA:
|
||||
csbk = new CapacityMaxAloha(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
|
||||
@@ -89,16 +89,18 @@ public enum Opcode
|
||||
MOTOROLA_CAPPLUS_PREAMBLE(Vendor.MOTOROLA_CAPACITY_PLUS, 61, "PREAMBLE"),
|
||||
MOTOROLA_CAPPLUS_SYSTEM_STATUS(Vendor.MOTOROLA_CAPACITY_PLUS, 62, "SYSTEM STATUS"),
|
||||
|
||||
HYTERA_CSBKO_32(Vendor.HYTERA_8, 32, "T-III HYTERA 08 CSBKO 32"),
|
||||
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "T-III 08 ANNOUNCEMENT"),
|
||||
HYTERA_CSBKO_44(Vendor.HYTERA_8, 44, "T-III HYTERA 08 CSBKO 44"),
|
||||
HYTERA_CSBKO_47(Vendor.HYTERA_8, 47, "T-III HYTERA 08 CSBKO 47"),
|
||||
HYTERA_08_ACKNOWLEDGE(Vendor.HYTERA_8, 32, "HYTERA 08 ACKNOWLEDGE"),
|
||||
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "HYTERA 08 ANNOUNCEMENT"),
|
||||
HYTERA_08_CSBKO_44(Vendor.HYTERA_8, 44, "HYTERA 08 CSBKO 44"),
|
||||
HYTERA_08_CSBKO_47(Vendor.HYTERA_8, 47, "HYTERA 08 CSBKO 47"),
|
||||
|
||||
HYTERA_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA XPT SITE STATE"),
|
||||
HYTERA_ALOHA(Vendor.HYTERA_68, 25, "HYTERA ALOHA"),
|
||||
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "T-III 68 ANNOUNCEMENT"),
|
||||
HYTERA_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA XPT PREAMBLE"), //Opcode 54 and/or 55 - See patent on Hytera call forwarding
|
||||
HYTERA_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA CSBKO 62"),
|
||||
HYTERA_68_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA 68 XPT SITE STATE"),
|
||||
HYTERA_68_ALOHA(Vendor.HYTERA_68, 25, "HYTERA 68 ALOHA"),
|
||||
HYTERA_68_ACKNOWLEDGE(Vendor.HYTERA_68, 32, "HYTERA 68 ACKNOWLEDGE"),
|
||||
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "HYTERA 68 ANNOUNCEMENT"),
|
||||
//Opcode 54 and/or 55 - See patent on Hytera call forwarding
|
||||
HYTERA_68_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA 68 XPT PREAMBLE"),
|
||||
HYTERA_68_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA 68 CSBKO 62"),
|
||||
|
||||
|
||||
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.dmr.message.data.csbk.hytera;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hytera DMR Tier III - CSBKO 32 - Possibly Acknowledge Response
|
||||
*
|
||||
* Analysis: ... this was transmitted on the traffic channel while the mobile was at the end of a call, in the terminator
|
||||
* sequence. Since this comes from the SMS gateway 16776906, I wonder if this is a traffic channel message waiting notification?
|
||||
*/
|
||||
public class Hytera08Acknowledge extends CSBKMessage
|
||||
{
|
||||
private static final int TARGET_GROUP_INDIVIDUAL_FLAG = 16;
|
||||
protected static final int[] RESPONSE_INFO = new int[]{16, 17, 18, 19, 20, 21, 22};
|
||||
private static final int[] ACKNOWLEDGE_TYPE = new int[]{23, 24};
|
||||
//Note: acknowledge type is the first 2 bits of the reason code - intentional overlap
|
||||
private static final int[] REASON_CODE = new int[]{23, 24, 25, 26, 27, 28, 29, 30};
|
||||
private static final int RESERVED = 31;
|
||||
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
|
||||
47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] ADDITIONAL_INFO_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
|
||||
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||
|
||||
protected List<Identifier> mIdentifiers;
|
||||
private IntegerIdentifier mTargetAddress;
|
||||
private RadioIdentifier mSourceRadio;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param syncPattern for the CSBK
|
||||
* @param message bits
|
||||
* @param cach for the DMR burst
|
||||
* @param slotType for this message
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public Hytera08Acknowledge(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" HYTERA 08 ").append(getAcknowledgeType());
|
||||
sb.append(" REASON:");
|
||||
|
||||
if(getReason() == Reason.UNKNOWN)
|
||||
{
|
||||
sb.append("UNKNOWN REASON CODE:").append(getMessage().getInt(REASON_CODE));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(getReason());
|
||||
}
|
||||
sb.append(" FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getTargetAddress());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the target address is a talkgroup (true) or radio/gateway (false).
|
||||
*/
|
||||
public boolean isTargetAddressTalkgroup()
|
||||
{
|
||||
return getMessage().get(TARGET_GROUP_INDIVIDUAL_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledgement message type
|
||||
*/
|
||||
public AcknowledgeType getAcknowledgeType()
|
||||
{
|
||||
return AcknowledgeType.fromValue(getMessage().getInt(ACKNOWLEDGE_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reason code.
|
||||
*
|
||||
* Note: the reason entries are coupled with the acknowledgement type and can optionally indicate when the
|
||||
* additional information field contains amplifying information.
|
||||
*/
|
||||
public Reason getReason()
|
||||
{
|
||||
return getReason(getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to determine the reason code for the specified message
|
||||
* @param message containing a reason code field
|
||||
* @return reason
|
||||
*/
|
||||
public static Reason getReason(CorrectedBinaryMessage message)
|
||||
{
|
||||
return Reason.fromValue(message.getInt(REASON_CODE));
|
||||
}
|
||||
|
||||
public IntegerIdentifier getTargetAddress()
|
||||
{
|
||||
if(mTargetAddress == null)
|
||||
{
|
||||
if(isTargetAddressTalkgroup())
|
||||
{
|
||||
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
|
||||
}
|
||||
else
|
||||
{
|
||||
mTargetAddress = DmrTier3Radio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
}
|
||||
}
|
||||
|
||||
return mTargetAddress;
|
||||
}
|
||||
|
||||
public boolean hasSourceRadio()
|
||||
{
|
||||
return true; //TODO: delineate valid reason codes here
|
||||
}
|
||||
|
||||
/**
|
||||
* Source radio identifier
|
||||
*
|
||||
* Note: the source radio field is dual-purpose and contains either a source radio address or
|
||||
* additional information, depending on the reason code and/or acknowledgement type. Use the
|
||||
* hasSourceRadio() method to first check if this message carries a source radio identifier.
|
||||
*/
|
||||
public RadioIdentifier getSourceRadio()
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(ADDITIONAL_INFO_SOURCE_ADDRESS));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTargetAddress());
|
||||
|
||||
if(hasSourceRadio())
|
||||
{
|
||||
mIdentifiers.add(getSourceRadio());
|
||||
}
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.dmr.message.data.csbk.hytera;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hytera FID68 - CSBKO 32 - Probable Acknowledge
|
||||
*
|
||||
* Analysis: ... this was transmitted on the Tier III control channel
|
||||
*/
|
||||
public class Hytera68Acknowledge extends CSBKMessage
|
||||
{
|
||||
private static final int TARGET_GROUP_INDIVIDUAL_FLAG = 16;
|
||||
protected static final int[] RESPONSE_INFO = new int[]{16, 17, 18, 19, 20, 21, 22};
|
||||
private static final int[] ACKNOWLEDGE_TYPE = new int[]{23, 24};
|
||||
//Note: acknowledge type is the first 2 bits of the reason code - intentional overlap
|
||||
private static final int[] REASON_CODE = new int[]{23, 24, 25, 26, 27, 28, 29, 30};
|
||||
private static final int RESERVED = 31;
|
||||
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
|
||||
47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] ADDITIONAL_INFO_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
|
||||
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||
|
||||
protected List<Identifier> mIdentifiers;
|
||||
private IntegerIdentifier mTargetAddress;
|
||||
private RadioIdentifier mSourceRadio;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param syncPattern for the CSBK
|
||||
* @param message bits
|
||||
* @param cach for the DMR burst
|
||||
* @param slotType for this message
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public Hytera68Acknowledge(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" HYTERA 68 ").append(getAcknowledgeType());
|
||||
sb.append(" REASON:");
|
||||
|
||||
if(getReason() == Reason.UNKNOWN)
|
||||
{
|
||||
sb.append("UNKNOWN REASON CODE:").append(getMessage().getInt(REASON_CODE));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(getReason());
|
||||
}
|
||||
sb.append(" FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getTargetAddress());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the target address is a talkgroup (true) or radio/gateway (false).
|
||||
*/
|
||||
public boolean isTargetAddressTalkgroup()
|
||||
{
|
||||
return getMessage().get(TARGET_GROUP_INDIVIDUAL_FLAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledgement message type
|
||||
*/
|
||||
public AcknowledgeType getAcknowledgeType()
|
||||
{
|
||||
return AcknowledgeType.fromValue(getMessage().getInt(ACKNOWLEDGE_TYPE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reason code.
|
||||
*
|
||||
* Note: the reason entries are coupled with the acknowledgement type and can optionally indicate when the
|
||||
* additional information field contains amplifying information.
|
||||
*/
|
||||
public Reason getReason()
|
||||
{
|
||||
return getReason(getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to determine the reason code for the specified message
|
||||
* @param message containing a reason code field
|
||||
* @return reason
|
||||
*/
|
||||
public static Reason getReason(CorrectedBinaryMessage message)
|
||||
{
|
||||
return Reason.fromValue(message.getInt(REASON_CODE));
|
||||
}
|
||||
|
||||
public IntegerIdentifier getTargetAddress()
|
||||
{
|
||||
if(mTargetAddress == null)
|
||||
{
|
||||
if(isTargetAddressTalkgroup())
|
||||
{
|
||||
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
|
||||
}
|
||||
else
|
||||
{
|
||||
mTargetAddress = DmrTier3Radio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
}
|
||||
}
|
||||
|
||||
return mTargetAddress;
|
||||
}
|
||||
|
||||
public boolean hasSourceRadio()
|
||||
{
|
||||
return true; //TODO: delineate valid reason codes here
|
||||
}
|
||||
|
||||
/**
|
||||
* Source radio identifier
|
||||
*
|
||||
* Note: the source radio field is dual-purpose and contains either a source radio address or
|
||||
* additional information, depending on the reason code and/or acknowledgement type. Use the
|
||||
* hasSourceRadio() method to first check if this message carries a source radio identifier.
|
||||
*/
|
||||
public RadioIdentifier getSourceRadio()
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(ADDITIONAL_INFO_SOURCE_ADDRESS));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTargetAddress());
|
||||
|
||||
if(hasSourceRadio())
|
||||
{
|
||||
mIdentifiers.add(getSourceRadio());
|
||||
}
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
@@ -155,7 +155,7 @@ public class HyteraAloha extends CSBKMessage
|
||||
{
|
||||
if(mRadioIdentifier == null)
|
||||
{
|
||||
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
|
||||
mRadioIdentifier = DmrTier3Radio.createTo(getMessage().getInt(RADIO));
|
||||
}
|
||||
|
||||
return mRadioIdentifier;
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.dmr.message.data.csbk.hytera;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hytera DMR Tier III - CSBKO 32 - Possibly Acknowledge Response
|
||||
*
|
||||
* Analysis: ... this was transmitted on the traffic channel while the mobile was at the end of a call, in the terminator
|
||||
* sequence. Since this comes from the SMS gateway 16776906, I wonder if this is a traffic channel message waiting notification?
|
||||
*/
|
||||
public class HyteraCsbko32 extends CSBKMessage
|
||||
{
|
||||
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||
49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||
73, 74, 75, 76, 77, 78, 79};
|
||||
|
||||
private List<Identifier> mIdentifiers;
|
||||
private RadioIdentifier mDestinationRadio;
|
||||
private RadioIdentifier mSourceRadio;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param syncPattern for the CSBK
|
||||
* @param message bits
|
||||
* @param cach for the DMR burst
|
||||
* @param slotType for this message
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public HyteraCsbko32(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" HYTERA CSBKO=32 PROBABLE ACKNOWLEDGE RESPONSE");
|
||||
sb.append(" FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationRadio());
|
||||
sb.append(" UNK1:").append(getUnknown1());
|
||||
sb.append(" UNK2:").append(getUnknown2());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Addressed (ie TO) radio identifier
|
||||
* @return radio identifier
|
||||
*/
|
||||
public RadioIdentifier getDestinationRadio()
|
||||
{
|
||||
if(mDestinationRadio == null)
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||
}
|
||||
|
||||
return mDestinationRadio;
|
||||
}
|
||||
|
||||
public String getUnknown1()
|
||||
{
|
||||
return getMessage().getHex(UNKNOWN_1, 2);
|
||||
}
|
||||
|
||||
public String getUnknown2()
|
||||
{
|
||||
return getMessage().getHex(UNKNOWN_2, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
|
||||
*/
|
||||
public RadioIdentifier getSourceRadio()
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getDestinationRadio());
|
||||
mIdentifiers.add(getSourceRadio());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
@@ -94,7 +94,7 @@ public class HyteraCsbko47 extends CSBKMessage
|
||||
{
|
||||
if(mDestinationRadio == null)
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||
}
|
||||
|
||||
return mDestinationRadio;
|
||||
@@ -117,7 +117,7 @@ public class HyteraCsbko47 extends CSBKMessage
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
|
||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
@@ -33,18 +33,16 @@ import java.util.List;
|
||||
/**
|
||||
* Hytera DMR Tier III - CSBKO 62 -
|
||||
*
|
||||
* Analysis: this message gets sent on timeslot 2 (not the Tier III control timeslot 1) from the SMS gateway (16776906)
|
||||
* and gets sent to each of the radios involved in the SMS transfer (sender/receiver). I suspect that the network tells
|
||||
* each of the radios to join the control channel and prepare for the SMS message.
|
||||
* Analysis: this message was observed sent on timeslot 2 (not the Tier III control timeslot 1) from the SMS gateway (16776906)
|
||||
* and gets sent to each of the radios involved in the SMS transfer (sender/receiver).
|
||||
*/
|
||||
public class HyteraSmsWaitingNotification extends CSBKMessage
|
||||
public class HyteraSmsAvailableNotification extends CSBKMessage
|
||||
{
|
||||
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||
49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||
73, 74, 75, 76, 77, 78, 79};
|
||||
private static final int[] UNKNOWN = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
|
||||
47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
|
||||
72, 73, 74, 75, 76, 77, 78, 79};
|
||||
|
||||
private List<Identifier> mIdentifiers;
|
||||
private RadioIdentifier mDestinationRadio;
|
||||
@@ -57,10 +55,11 @@ public class HyteraSmsWaitingNotification extends CSBKMessage
|
||||
* @param message bits
|
||||
* @param cach for the DMR burst
|
||||
* @param slotType for this message
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
* @param timestamp of the message
|
||||
* @param timeslot of the message
|
||||
*/
|
||||
public HyteraSmsWaitingNotification(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
public HyteraSmsAvailableNotification(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
|
||||
SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
@@ -76,11 +75,9 @@ public class HyteraSmsWaitingNotification extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" HYTERA PREPARE FOR SMS MESSAGE FOR:").append(getDestinationRadio());
|
||||
sb.append(" HYTERA SMS MESSAGE AVAILABLE FOR:").append(getDestinationRadio());
|
||||
sb.append(" FM:").append(getSourceRadio());
|
||||
sb.append(" UNK1:").append(getUnknown1());
|
||||
sb.append(" UNK2:").append(getUnknown2());
|
||||
|
||||
sb.append(" UNK:").append(getUnknown());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
return sb.toString();
|
||||
@@ -94,20 +91,19 @@ public class HyteraSmsWaitingNotification extends CSBKMessage
|
||||
{
|
||||
if(mDestinationRadio == null)
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||
}
|
||||
|
||||
return mDestinationRadio;
|
||||
}
|
||||
|
||||
public String getUnknown1()
|
||||
/**
|
||||
* Unknown octets.
|
||||
* @return two unknown octets as hex string
|
||||
*/
|
||||
public String getUnknown()
|
||||
{
|
||||
return getMessage().getHex(UNKNOWN_1, 2);
|
||||
}
|
||||
|
||||
public String getUnknown2()
|
||||
{
|
||||
return getMessage().getHex(UNKNOWN_2, 2);
|
||||
return getMessage().getHex(UNKNOWN, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +113,7 @@ public class HyteraSmsWaitingNotification extends CSBKMessage
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
@@ -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,12 +24,11 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -153,7 +152,7 @@ public class HyteraXPTPreamble extends Preamble
|
||||
}
|
||||
else
|
||||
{
|
||||
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
mTargetAddress = DmrTier3Radio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +166,7 @@ public class HyteraXPTPreamble extends Preamble
|
||||
{
|
||||
if(mSourceAddress == null)
|
||||
{
|
||||
mSourceAddress = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
|
||||
mSourceAddress = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
|
||||
}
|
||||
|
||||
return mSourceAddress;
|
||||
|
||||
@@ -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
|
||||
@@ -23,14 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Version;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -83,7 +82,7 @@ public class CapacityMaxAloha extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CAPACITY-MAX ALOHA");
|
||||
sb.append(" CSBK CAPACITY-MAX ALOHA");
|
||||
|
||||
if(hasRadioIdentifier())
|
||||
{
|
||||
@@ -153,7 +152,7 @@ public class CapacityMaxAloha extends CSBKMessage
|
||||
{
|
||||
if(mRadioIdentifier == null)
|
||||
{
|
||||
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
|
||||
mRadioIdentifier = DmrTier3Radio.createTo(getMessage().getInt(RADIO));
|
||||
}
|
||||
|
||||
return mRadioIdentifier;
|
||||
|
||||
@@ -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
|
||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -60,7 +59,7 @@ public class CapacityPlusCSBKO_60 extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CAP+ *UNKNOWN* CSBK:60");
|
||||
sb.append(" CSBK CAP+ *UNKNOWN* CSBK:60");
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -89,7 +88,7 @@ public class CapacityPlusDataRevertWindowAnnouncement extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" MOTOROLA CAP+ ENHANCED DATA REVERT ANNOUNCEMENT");
|
||||
sb.append(" CSBK CAP+ ENHANCED DATA REVERT ANNOUNCEMENT");
|
||||
sb.append(" WINDOW:").append(getSuperFrame()).append(".").append(getWindow());
|
||||
|
||||
if(hasTargetRadio())
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -72,7 +71,7 @@ public class CapacityPlusDataRevertWindowGrant extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" MOTOROLA CAP+ ENHANCED DATA REVERT GRANT TO:").append(getTargetAddress());
|
||||
sb.append(" CSBK CAP+ ENHANCED DATA REVERT GRANT TO:").append(getTargetAddress());
|
||||
sb.append(" TRANSMIT IN WINDOW:").append(getSuperFrame()).append(".").append(getWindow());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -30,7 +30,6 @@ import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -90,7 +89,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" MOTOROLA CAP+ SITE:").append(getSite());
|
||||
sb.append(" CSBK CAP+ SITE:").append(getSite());
|
||||
sb.append(" REST ").append(getRestChannel());
|
||||
sb.append(" FL:").append(getLCSS());
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -76,7 +75,7 @@ public class CapacityPlusPreamble extends Preamble
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" MOTOROLA CAP+").append(isCSBKPreamble() ? " CSBK" : " DATA");
|
||||
sb.append(" CSBK CAP+").append(isCSBKPreamble() ? " CSBK" : " DATA");
|
||||
sb.append(" PREAMBLE FM:").append(getSourceAddress());
|
||||
sb.append(" TO:").append(getTargetAddress());
|
||||
sb.append(" BLOCKS TO FOLLOW:").append(getBlocksToFollow());
|
||||
|
||||
@@ -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
|
||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -72,7 +71,7 @@ public class CapacityPlusSystemStatus extends CSBKMessage implements ITimeslotFr
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" MOTOROLA CAP+ SYSTEM STATUS ").append(getFragmentIndicator());
|
||||
sb.append(" CSBK CAP+ SYSTEM STATUS ").append(getFragmentIndicator());
|
||||
sb.append(" REST ").append(getRestChannel());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -72,7 +71,7 @@ public class ConnectPlusCSBKO_16 extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ CSBKO:16 FM:").append(getSourceRadio());
|
||||
sb.append(" CSBK CON+ CSBKO:16 FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getTargetRadio());
|
||||
sb.append(" UNK:").append(getUnknown());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -71,7 +70,7 @@ public class ConnectPlusDataRevertWindowAnnouncement extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ ENHANCED DATA REVERT CHANNEL ANNOUNCE WINDOW:");
|
||||
sb.append(" CSBK CON+ ENHANCED DATA REVERT CHANNEL ANNOUNCE WINDOW:");
|
||||
sb.append(getSuperFrame()).append(".").append(getWindow());
|
||||
if(hasTargetRadio())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -72,7 +71,7 @@ public class ConnectPlusDataRevertWindowGrant extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ ENHANCED DATA REVERT GRANT TO:").append(getTargetRadio());
|
||||
sb.append(" CSBK CON+ ENHANCED DATA REVERT GRANT TO:").append(getTargetRadio());
|
||||
sb.append(" TRANSMIT IN WINDOW:").append(getSuperFrame()).append(".").append(getWindow());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
|
||||
@@ -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
|
||||
@@ -28,7 +28,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -70,7 +69,7 @@ public class ConnectPlusNeighborReport extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ NEIGHBORS:");
|
||||
sb.append(" CSBK CON+ NEIGHBORS:");
|
||||
List<SiteIdentifier> neighbors = getNeighbors();
|
||||
|
||||
if(neighbors.isEmpty())
|
||||
|
||||
@@ -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
|
||||
@@ -28,8 +28,7 @@ import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.ip.xcmp.XCMPMessageType;
|
||||
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPMessageType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -78,7 +77,7 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ ANNOUNCE OTA ").append(getMessageType());
|
||||
sb.append(" CSBK CON+ ANNOUNCE OTA ").append(getMessageType());
|
||||
sb.append(" VER:").append(getMessageVersion());
|
||||
sb.append(" AVAILABLE ON ").append(getDataChannel());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -72,7 +71,7 @@ public class ConnectPlusRegistrationRequest extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ REGISTRATION REQUEST FM:").append(getSourceRadio());
|
||||
sb.append(" CSBK CON+ REGISTRATION REQUEST FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getTargetRadio());
|
||||
sb.append(" UNK:").append(getUnknown());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
@@ -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
|
||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -72,7 +71,7 @@ public class ConnectPlusRegistrationResponse extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ REGISTRATION RESPONSE FM:").append(getSourceRadio());
|
||||
sb.append(" CSBK CON+ REGISTRATION RESPONSE FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getTargetRadio());
|
||||
sb.append(" UNK:").append(getUnknown());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
@@ -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
|
||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -74,7 +73,7 @@ public class ConnectPlusTalkgroupAffiliation extends CSBKMessage
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ AFFILIATE RADIO:").append(getRadio());
|
||||
sb.append(" CSBK CON+ AFFILIATE RADIO:").append(getRadio());
|
||||
sb.append(" TO TALKGROUP:").append(getTalkgroup());
|
||||
sb.append(" UNK:").append(getUnknown());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
|
||||
@@ -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
|
||||
@@ -32,7 +32,6 @@ import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -80,7 +79,7 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" CON+ VOICE CHANNEL USER FM:").append(getRadio());
|
||||
sb.append(" CSBK CON+ VOICE CHANNEL USER FM:").append(getRadio());
|
||||
sb.append(" TO:").append(getTalkgroup());
|
||||
sb.append(" ").append(getChannel());
|
||||
sb.append(" UNK:").append(getUnknownField());
|
||||
|
||||
@@ -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
|
||||
@@ -23,14 +23,13 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Version;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -153,7 +152,7 @@ public class Aloha extends CSBKMessage
|
||||
{
|
||||
if(mRadioIdentifier == null)
|
||||
{
|
||||
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
|
||||
mRadioIdentifier = DmrTier3Radio.createTo(getMessage().getInt(RADIO));
|
||||
}
|
||||
|
||||
return mRadioIdentifier;
|
||||
|
||||
@@ -29,8 +29,8 @@ import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
@@ -141,7 +141,7 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
@@ -157,7 +157,7 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationId = DMRRadio.createTo(getMessage().getInt(DESTINATION));
|
||||
mDestinationId = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -25,14 +25,13 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -178,7 +177,7 @@ public class MoveTSCC extends CSBKMessage
|
||||
{
|
||||
if(mRadioIdentifier == null)
|
||||
{
|
||||
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
|
||||
mRadioIdentifier = DmrTier3Radio.createTo(getMessage().getInt(RADIO));
|
||||
}
|
||||
|
||||
return mRadioIdentifier;
|
||||
|
||||
@@ -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,12 +24,11 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -126,7 +125,7 @@ public class Preamble extends CSBKMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
mTargetAddress = DmrTier3Radio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +139,7 @@ public class Preamble extends CSBKMessage
|
||||
{
|
||||
if(mSourceAddress == null)
|
||||
{
|
||||
mSourceAddress = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
|
||||
mSourceAddress = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
|
||||
}
|
||||
|
||||
return mSourceAddress;
|
||||
|
||||
@@ -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,13 +24,12 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ProtectKind;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -104,7 +103,7 @@ public class Protect extends CSBKMessage
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
@@ -123,7 +122,7 @@ public class Protect extends CSBKMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationId = DMRRadio.createTo(getMessage().getInt(DESTINATION));
|
||||
mDestinationId = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,14 +24,13 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -136,7 +135,7 @@ public class Acknowledge extends CSBKMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
mTargetAddress = DmrTier3Radio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +158,7 @@ public class Acknowledge extends CSBKMessage
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(ADDITIONAL_INFO_SOURCE_ADDRESS));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(ADDITIONAL_INFO_SOURCE_ADDRESS));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
|
||||
@@ -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
|
||||
@@ -22,8 +22,8 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
@@ -117,7 +117,7 @@ public abstract class Ahoy extends CSBKMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
mTargetAddress = DmrTier3Radio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -26,8 +26,7 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Gateway;
|
||||
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -84,9 +83,9 @@ public class AuthenticateRegisterRadioCheck extends Ahoy
|
||||
/**
|
||||
* Gateway address from the multi-purpose field
|
||||
*/
|
||||
public Gateway getGateway()
|
||||
public Tier3Gateway getGateway()
|
||||
{
|
||||
return Gateway.fromValue(getMultiPurposeFieldValue());
|
||||
return Tier3Gateway.fromValue(getMultiPurposeFieldValue());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +107,7 @@ public class AuthenticateRegisterRadioCheck extends Ahoy
|
||||
*/
|
||||
public boolean isAuthenticateCommand()
|
||||
{
|
||||
return getGateway() != Gateway.TSI;
|
||||
return getGateway() != Tier3Gateway.TSI;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -26,8 +26,7 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Gateway;
|
||||
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -80,9 +79,9 @@ public class StunReviveKill extends Ahoy
|
||||
/**
|
||||
* Gateway address from the multi-purpose field
|
||||
*/
|
||||
public Gateway getGateway()
|
||||
public Tier3Gateway getGateway()
|
||||
{
|
||||
return Gateway.fromValue(getMultiPurposeFieldValue());
|
||||
return Tier3Gateway.fromValue(getMultiPurposeFieldValue());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -26,8 +26,7 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Gateway;
|
||||
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -81,9 +80,9 @@ public class UnknownAhoy extends Ahoy
|
||||
/**
|
||||
* Gateway address from the multi-purpose field
|
||||
*/
|
||||
public Gateway getGateway()
|
||||
public Tier3Gateway getGateway()
|
||||
{
|
||||
return Gateway.fromValue(getMultiPurposeFieldValue());
|
||||
return Tier3Gateway.fromValue(getMultiPurposeFieldValue());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -23,10 +23,9 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -102,7 +101,7 @@ public class MassRegistration extends Announcement
|
||||
{
|
||||
if(mDestinationRadio == null)
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||
}
|
||||
|
||||
return mDestinationRadio;
|
||||
|
||||
@@ -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
|
||||
@@ -23,11 +23,10 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -121,11 +120,11 @@ public class DuplexPrivateDataChannelGrant extends ChannelGrant
|
||||
{
|
||||
if(getMessage().get(TARGET_IS_SOURCE_ADDRESS_FLAG))
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(DESTINATION));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(DESTINATION));
|
||||
}
|
||||
else
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,11 +140,11 @@ public class DuplexPrivateDataChannelGrant extends ChannelGrant
|
||||
{
|
||||
if(getMessage().get(TARGET_IS_SOURCE_ADDRESS_FLAG))
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(SOURCE));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(SOURCE));
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -23,11 +23,10 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -121,11 +120,11 @@ public class DuplexPrivateVoiceChannelGrant extends ChannelGrant
|
||||
{
|
||||
if(getMessage().get(TARGET_IS_SOURCE_ADDRESS_FLAG))
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(DESTINATION));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(DESTINATION));
|
||||
}
|
||||
else
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,11 +140,11 @@ public class DuplexPrivateVoiceChannelGrant extends ChannelGrant
|
||||
{
|
||||
if(getMessage().get(TARGET_IS_SOURCE_ADDRESS_FLAG))
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(SOURCE));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(SOURCE));
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -23,11 +23,10 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -132,7 +131,7 @@ public class PrivateDataChannelGrant extends ChannelGrant
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
@@ -145,7 +144,7 @@ public class PrivateDataChannelGrant extends ChannelGrant
|
||||
{
|
||||
if(mDestinationRadio == null)
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION));
|
||||
}
|
||||
|
||||
return mDestinationRadio;
|
||||
|
||||
@@ -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
|
||||
@@ -23,11 +23,10 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -118,7 +117,7 @@ public class PrivateVoiceChannelGrant extends ChannelGrant
|
||||
{
|
||||
if(mSourceRadio == null)
|
||||
{
|
||||
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
|
||||
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE));
|
||||
}
|
||||
|
||||
return mSourceRadio;
|
||||
@@ -131,7 +130,7 @@ public class PrivateVoiceChannelGrant extends ChannelGrant
|
||||
{
|
||||
if(mDestinationRadio == null)
|
||||
{
|
||||
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION));
|
||||
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION));
|
||||
}
|
||||
|
||||
return mDestinationRadio;
|
||||
|
||||
@@ -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
|
||||
@@ -26,8 +26,8 @@ import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -36,6 +36,8 @@ import java.util.List;
|
||||
*/
|
||||
public class MBCHeader extends CSBKMessage
|
||||
{
|
||||
private static final int[] ANNOUNCEMENT_TYPE = new int[]{16, 17, 18, 19, 20};
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
@@ -87,7 +89,7 @@ public class MBCHeader extends CSBKMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" VENDOR:").append(vendor);
|
||||
sb.append(" ").append(vendor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +99,10 @@ public class MBCHeader extends CSBKMessage
|
||||
{
|
||||
sb.append(" UNKNOWN CSBKO:").append(getOpcodeValue());
|
||||
}
|
||||
else if(opcode == Opcode.HYTERA_08_ANNOUNCEMENT)
|
||||
{
|
||||
sb.append(" ANNOUNCEMENT:").append(AnnouncementType.fromValue(getMessage().getInt(ANNOUNCEMENT_TYPE)));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" ").append(opcode);
|
||||
|
||||
@@ -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,8 +23,8 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
|
||||
@@ -104,7 +104,7 @@ public abstract class PacketSequenceHeader extends DataHeader
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationLLID = DMRRadio.createTo(getMessage().getInt(DESTINATION_IDENTIFIER));
|
||||
mDestinationLLID = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_IDENTIFIER));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ public abstract class PacketSequenceHeader extends DataHeader
|
||||
{
|
||||
if(mSourceLLID == null)
|
||||
{
|
||||
mSourceLLID = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
mSourceLLID = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
}
|
||||
|
||||
return mSourceLLID;
|
||||
|
||||
@@ -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,12 +24,12 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ResponseStatus;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -47,6 +47,7 @@ public class ResponseDataHeader extends DataHeader
|
||||
private static final int[] BLOCKS_TO_FOLLOW = new int[]{65, 66, 67, 68, 69, 70, 71};
|
||||
private static final int[] CLASS_TYPE_STATUS = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||
|
||||
private ResponseStatus mResponseStatus;
|
||||
private IntegerIdentifier mDestinationLLID;
|
||||
private RadioIdentifier mSourceLLID;
|
||||
private List<Identifier> mIdentifiers;
|
||||
@@ -79,16 +80,29 @@ public class ResponseDataHeader extends DataHeader
|
||||
sb.append(" FM:").append(getSourceLLID());
|
||||
sb.append(" TO:").append(getDestinationLLID());
|
||||
sb.append(" ").append(getServiceAccessPoint());
|
||||
sb.append(" CLASS/TYPE/STATUS:").append(getClassTypeStatus());
|
||||
sb.append(" BLOCKS TO FOLLOW:").append(getBlocksToFollow());
|
||||
sb.append(" STATUS:").append(getResponseStatus());
|
||||
sb.append(" - BLOCKS TO FOLLOW:").append(getBlocksToFollow());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of the response code.
|
||||
* @return response status description.
|
||||
*/
|
||||
public ResponseStatus getResponseStatus()
|
||||
{
|
||||
if(mResponseStatus == null)
|
||||
{
|
||||
mResponseStatus = new ResponseStatus(getClassTypeStatus());
|
||||
}
|
||||
|
||||
return mResponseStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class, Type and Status of response.
|
||||
*
|
||||
* TODO: create an enumeration for this value - see TS 102 361-3 for definitions
|
||||
* @return
|
||||
* @return integer value of the field.
|
||||
*/
|
||||
public int getClassTypeStatus()
|
||||
{
|
||||
@@ -132,7 +146,7 @@ public class ResponseDataHeader extends DataHeader
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationLLID = DMRRadio.createTo(getMessage().getInt(DESTINATION_IDENTIFIER));
|
||||
mDestinationLLID = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_IDENTIFIER));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +161,7 @@ public class ResponseDataHeader extends DataHeader
|
||||
{
|
||||
if(mSourceLLID == null)
|
||||
{
|
||||
mSourceLLID = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
mSourceLLID = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
}
|
||||
|
||||
return mSourceLLID;
|
||||
|
||||
@@ -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,12 +24,11 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -134,7 +133,7 @@ public abstract class ShortDataHeader extends OctetDataHeader
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationLLID = DMRRadio.createTo(getMessage().getInt(DESTINATION_IDENTIFIER));
|
||||
mDestinationLLID = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_IDENTIFIER));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +148,7 @@ public abstract class ShortDataHeader extends OctetDataHeader
|
||||
{
|
||||
if(mSourceLLID == null)
|
||||
{
|
||||
mSourceLLID = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
mSourceLLID = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
}
|
||||
|
||||
return mSourceLLID;
|
||||
|
||||
@@ -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,13 +24,12 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRUnitStatus;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -158,7 +157,7 @@ public class StatusDataHeader extends DataHeader
|
||||
}
|
||||
else
|
||||
{
|
||||
mDestinationLLID = DMRRadio.createTo(getMessage().getInt(DESTINATION_IDENTIFIER));
|
||||
mDestinationLLID = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_IDENTIFIER));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +172,7 @@ public class StatusDataHeader extends DataHeader
|
||||
{
|
||||
if(mSourceLLID == null)
|
||||
{
|
||||
mSourceLLID = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
mSourceLLID = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||
}
|
||||
|
||||
return mSourceLLID;
|
||||
|
||||
@@ -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
|
||||
@@ -30,9 +30,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataH
|
||||
*/
|
||||
public class HyteraProprietaryDataHeader extends ProprietaryDataHeader
|
||||
{
|
||||
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
@@ -52,27 +49,14 @@ public class HyteraProprietaryDataHeader extends ProprietaryDataHeader
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("HYTERA PROPRIETARY DATA HEADER");
|
||||
sb.append(" ").append(getServiceAccessPoint());
|
||||
sb.append(" UNK1:").append(getUnknown1());
|
||||
sb.append(" UNK2:").append(getUnknown2());
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
}
|
||||
sb.append(" HYTERA PROPRIETARY DATA HEADER");
|
||||
sb.append(" SAP:").append(getServiceAccessPoint());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown 8-bit field. This is likely the encryption type identifier.
|
||||
*/
|
||||
public int getUnknown1()
|
||||
{
|
||||
return getMessage().getInt(UNKNOWN_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown 8-bit field. This is likely the encryption key identifier.
|
||||
*/
|
||||
public int getUnknown2()
|
||||
{
|
||||
return getMessage().getInt(UNKNOWN_2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.mbc;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,10 +30,10 @@ import java.util.List;
|
||||
*/
|
||||
public class MBCAssembler
|
||||
{
|
||||
private MBCHeader mTS0Header;
|
||||
private MBCHeader mTS1Header;
|
||||
private List<MBCContinuationBlock> mTS0ContinuationBlocks = new ArrayList<>();
|
||||
private MBCHeader mTS2Header;
|
||||
private List<MBCContinuationBlock> mTS1ContinuationBlocks = new ArrayList<>();
|
||||
private List<MBCContinuationBlock> mTS2ContinuationBlocks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -53,14 +52,14 @@ public class MBCAssembler
|
||||
{
|
||||
reset(header.getTimeslot());
|
||||
|
||||
if(header.getTimeslot() == 0)
|
||||
{
|
||||
mTS0Header = header;
|
||||
}
|
||||
else
|
||||
if(header.getTimeslot() == 1)
|
||||
{
|
||||
mTS1Header = header;
|
||||
}
|
||||
else if(header.getTimeslot() == 2)
|
||||
{
|
||||
mTS2Header = header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,25 +72,7 @@ public class MBCAssembler
|
||||
{
|
||||
if(continuationBlock != null)
|
||||
{
|
||||
if(continuationBlock.getTimeslot() == 0)
|
||||
{
|
||||
mTS0ContinuationBlocks.add(continuationBlock);
|
||||
|
||||
if(continuationBlock.isLastBlock())
|
||||
{
|
||||
if(mTS0Header != null)
|
||||
{
|
||||
CSBKMessage csbk = CSBKMessageFactory.create(mTS0Header, new ArrayList<>(mTS0ContinuationBlocks));
|
||||
reset(0);
|
||||
return csbk;
|
||||
}
|
||||
else
|
||||
{
|
||||
reset(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if(continuationBlock.getTimeslot() == 1)
|
||||
{
|
||||
mTS1ContinuationBlocks.add(continuationBlock);
|
||||
|
||||
@@ -109,6 +90,24 @@ public class MBCAssembler
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(continuationBlock.getTimeslot() == 2)
|
||||
{
|
||||
mTS2ContinuationBlocks.add(continuationBlock);
|
||||
|
||||
if(continuationBlock.isLastBlock())
|
||||
{
|
||||
if(mTS2Header != null)
|
||||
{
|
||||
CSBKMessage csbk = CSBKMessageFactory.create(mTS2Header, new ArrayList<>(mTS2ContinuationBlocks));
|
||||
reset(2);
|
||||
return csbk;
|
||||
}
|
||||
else
|
||||
{
|
||||
reset(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -120,15 +119,15 @@ public class MBCAssembler
|
||||
*/
|
||||
public void reset(int timeslot)
|
||||
{
|
||||
if(timeslot == 0)
|
||||
{
|
||||
mTS0Header = null;
|
||||
mTS0ContinuationBlocks.clear();
|
||||
}
|
||||
else
|
||||
if(timeslot == 1)
|
||||
{
|
||||
mTS1Header = null;
|
||||
mTS1ContinuationBlocks.clear();
|
||||
}
|
||||
else if(timeslot == 2)
|
||||
{
|
||||
mTS2Header = null;
|
||||
mTS2ContinuationBlocks.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2020 Zhenyu Mao
|
||||
* *****************************************************************************
|
||||
* 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.dmr.message.data.mbc;
|
||||
|
||||
@@ -25,7 +24,6 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -52,7 +50,19 @@ public class MBCContinuationBlock extends DataMessage
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" MULTI-BLOCK CSBK CONTINUATION FINAL BLOCK:").append(isLastBlock() ? "YES" : "NO");
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" [CRC ERROR]");
|
||||
}
|
||||
|
||||
sb.append(" MULTI-BLOCK CSBK CONTINUATION");
|
||||
|
||||
if(isLastBlock())
|
||||
{
|
||||
sb.append("-FINAL");
|
||||
}
|
||||
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -31,11 +31,14 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraPro
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
|
||||
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
||||
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header;
|
||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
||||
import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.xcmp.XCMPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
@@ -60,6 +63,7 @@ public class PacketSequenceMessageFactory
|
||||
if(packetSequence.hasPacketSequenceHeader())
|
||||
{
|
||||
PacketSequenceHeader primaryHeader = packetSequence.getPacketSequenceHeader();
|
||||
boolean confirmed = primaryHeader.isConfirmedData();
|
||||
CorrectedBinaryMessage packet = getPacket(packetSequence, primaryHeader.isConfirmedData());
|
||||
|
||||
if(packet != null)
|
||||
@@ -136,6 +140,7 @@ public class PacketSequenceMessageFactory
|
||||
{
|
||||
HeaderMessage secondaryHeader = packetSequence.getProprietaryDataHeader();
|
||||
|
||||
//MotoTRBO MNIS
|
||||
if(secondaryHeader instanceof MNISProprietaryDataHeader)
|
||||
{
|
||||
ApplicationType applicationType = ((MNISProprietaryDataHeader)secondaryHeader).getApplicationType();
|
||||
@@ -162,8 +167,18 @@ public class PacketSequenceMessageFactory
|
||||
}
|
||||
else if(secondaryHeader instanceof HyteraProprietaryDataHeader)
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
|
||||
|
||||
if(hyteraTokenHeader.isSMSMessage())
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new HyteraSmsPacket(hyteraTokenHeader), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DMRPacketMessage(packetSequence, new HyteraUnknownPacket(hyteraTokenHeader), packet,
|
||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -266,4 +281,31 @@ public class PacketSequenceMessageFactory
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the first 7 bits of each data block to see if a contiguous data block sequence number pattern is
|
||||
* present. This might indicate that the payload is a confirmed payload versus an unconfirmed payload when
|
||||
* the packet sequence is not yet identified.
|
||||
*
|
||||
* @param dataBlocks to inspect.
|
||||
* @return true if each of the data blocks have a sequential data block sequencing number.
|
||||
*/
|
||||
public static boolean isConfirmedDataBlocks(List<DataBlock> dataBlocks)
|
||||
{
|
||||
int sequenceNumber = 0;
|
||||
|
||||
for(DataBlock dataBlock: dataBlocks)
|
||||
{
|
||||
if(dataBlock.getDataBlockSerialNumber() == sequenceNumber)
|
||||
{
|
||||
sequenceNumber++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -25,7 +25,7 @@ package io.github.dsheirer.module.decode.dmr.message.type;
|
||||
public enum AcknowledgeType
|
||||
{
|
||||
ACKNOWLEDGE(0, "ACKNOWLEDGED"),
|
||||
NOT_ACKNOWLEDGE(1, "REJECTED/REFUSED"),
|
||||
NOT_ACKNOWLEDGE(1, "REJECTED/REFUSED/NOT ACKNOWLEDGED"),
|
||||
QUEUED(2, "QUEUED"),
|
||||
WAIT(3, "WAIT"),
|
||||
UNKNOWN(-1, "UNKNOWN");
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.type;
|
||||
|
||||
/**
|
||||
* DMR Gateway Identifiers
|
||||
*
|
||||
* See: TS 102 361.4 paragraph A.4
|
||||
*/
|
||||
public enum Gateway
|
||||
{
|
||||
PSTNI(0xFFFEC0, "PSTN SERVICES - ALIGNED TIMING"),
|
||||
PABXI(0xFFFEC1, "PABX SERVICES - ALIGNED TIMING"),
|
||||
LINEI(0xFFFEC2, "LINE GATEWAY - ALIGNED TIMING"),
|
||||
IPI(0xFFFEC3, "IP GATEWAY - ALIGNED TIMING"),
|
||||
SUPLI(0xFFFEC4, "SUPPLEMENTARY DATA SERVICE"),
|
||||
SDMI(0xFFFEC5, "UDT SHORT DATA SERVICE"),
|
||||
REGI(0xFFFEC6, "REGISTRATION SERVICE"),
|
||||
MSI(0xFFFEC7, "CALL DIVERSION TO MS"),
|
||||
DIVERTI(0xFFFEC9, "CALL DIVERSION CANCEL"),
|
||||
TSI(0xFFFECA, "TRUNK SYSTEM CONTROLLER"),
|
||||
DISPATI(0xFFFECB, "SYSTEM DISPATCHER - ALIGNED TIMING"),
|
||||
STUNI(0xFFFECC, "STUN/REVIVE MS ID"),
|
||||
AUTHI(0xFFFECD, "AUTHENTICATION ID"),
|
||||
GPI(0xFFFECE, "CALL DIVERSION TO TALKGROUP"),
|
||||
KILLI(0xFFFECF, "KILL MS ID"),
|
||||
PSTNDI(0xFFFED0, "PSTN SERVICES - OFFSET TIMING"),
|
||||
PABXDI(0xFFFED1, "PABX SERVICES - OFFSET TIMING"),
|
||||
LINEDI(0xFFFED2, "LINE GATEWAY - OFFSET TIMING"),
|
||||
DISPATDI(0xFFFED3, "SYSTEM DISPATCHER - OFFSET TIMING"),
|
||||
ALLMIS(0xFFFED4, "ALL MS AND TALKGROUPS"),
|
||||
IPDI(0xFFFED5, "IP GATEWAY - OFFSET TIMING"),
|
||||
DGNAI(0xFFFED6, "DYNAMIC GROUP NUMBER ASSIGNMENT"),
|
||||
TATTSI(0xFFFED7, "TALKGROUP SUBSCRIBE/ATTACH SERVICE"),
|
||||
ALLMSIDL(0xFFFFFD, "ALL SITE MS AS TALKGROUP"),
|
||||
ALLMSIDZ(0xFFFFFE, "ALL SITE SUBSET MS AS TALKGROUP"),
|
||||
ALLMSID(0xFFFFFF, "ALL MS AS TALKGROUP"),
|
||||
UNKNOWN(-1, "UNKNOWN");
|
||||
|
||||
private int mAddress;
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param address of the entry
|
||||
* @param label for the entry
|
||||
*/
|
||||
Gateway(int address, String label)
|
||||
{
|
||||
mAddress = address;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address of the gateway identifier
|
||||
*/
|
||||
public int getAddress()
|
||||
{
|
||||
return mAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to lookup the gateway from a value.
|
||||
* @param value of the service type
|
||||
* @return type or UNKNOWN
|
||||
*/
|
||||
public static Gateway fromValue(int value)
|
||||
{
|
||||
if(0 <= value && value <= 15)
|
||||
{
|
||||
return Gateway.values()[value];
|
||||
}
|
||||
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.dmr.message.type;
|
||||
|
||||
/**
|
||||
* Hytera data encoding format values.
|
||||
*/
|
||||
public enum HyteraEncodeFormat
|
||||
{
|
||||
BINARY(0),
|
||||
MOBILE_SUBSCRIBER_NUMBER(1),
|
||||
BINARY_CODED_DECIMAL(2),
|
||||
ISO_7(3),
|
||||
ISO_8(4),
|
||||
NMEA_GPS(5),
|
||||
INTERNET_PROTOCOL(6),
|
||||
UNICODE(7),
|
||||
BYTES_8_BITS(8),
|
||||
GBK(10), //GB2312 Simplified Chinese Characters
|
||||
UNKNOWN(-1);
|
||||
|
||||
private int mValue;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param value
|
||||
*/
|
||||
HyteraEncodeFormat(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coded value for this format
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the element from the value.
|
||||
* @param value to lookup
|
||||
* @return element or UNKNOWN
|
||||
*/
|
||||
public static HyteraEncodeFormat fromValue(int value)
|
||||
{
|
||||
if(0 <= value && value <= 8)
|
||||
{
|
||||
return HyteraEncodeFormat.values()[value];
|
||||
}
|
||||
else if(value == 10)
|
||||
{
|
||||
return GBK;
|
||||
}
|
||||
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.dmr.message.type;
|
||||
|
||||
/**
|
||||
* Response header status parser class.
|
||||
*
|
||||
* ETSI TS 102 361-1 V2.5.1 Table 8.3: Response Packet Class, Type, and Status definitions
|
||||
*/
|
||||
public class ResponseStatus
|
||||
{
|
||||
private int mValue;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param value in the range of 0 - 255
|
||||
*/
|
||||
public ResponseStatus(int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status class indicating success, fail, or selective retry.
|
||||
* @return status.
|
||||
*/
|
||||
public Status getStatus()
|
||||
{
|
||||
return Status.fromValue(mValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of status message, specific to each status class.
|
||||
* @return
|
||||
*/
|
||||
public int getType()
|
||||
{
|
||||
return (mValue & 0x38) >> 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value of the Status field.
|
||||
* @return value
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return (mValue & 0x07);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
switch(getStatus())
|
||||
{
|
||||
case ACK:
|
||||
sb.append("SUCCESS - ALL BLOCKS THRU ").append(getValue()).append(" RECEIVED");
|
||||
break;
|
||||
case NACK:
|
||||
switch(getType())
|
||||
{
|
||||
case 0:
|
||||
sb.append("FAIL - ILLEGAL FORMAT - LAST VALID FSN:").append(getValue());
|
||||
break;
|
||||
case 1:
|
||||
sb.append("FAIL - PACKET CRC FAIL - LAST VALID FSN:").append(getValue());
|
||||
break;
|
||||
case 2:
|
||||
sb.append("FAIL - RECIPIENT MEMORY FULL - LAST VALID FSN:").append(getValue());
|
||||
break;
|
||||
case 3:
|
||||
sb.append("FAIL - OUT OF SEQUENCE FSN:").append(getValue());
|
||||
break;
|
||||
case 4:
|
||||
sb.append("FAIL - UNDELIVERABLE - LAST VALID FSN:").append(getValue());
|
||||
break;
|
||||
case 5:
|
||||
sb.append("FAIL - PACKET OUT OF SEQUENCE - LAST VALID FSN:").append(getValue());
|
||||
break;
|
||||
case 6:
|
||||
sb.append("FAIL - INVALID USER ON THIS SYSTEM");
|
||||
break;
|
||||
default:
|
||||
sb.append("FAIL - UNKNOWN TYPE [").append(getType()).append("] VALUE [").append(getValue()).append("]");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SACK:
|
||||
sb.append("SELECTIVE RETRY - LAST VALID FSN:").append(getValue());
|
||||
break;
|
||||
case RESERVED:
|
||||
sb.append("UNKNOWN/RESERVED WITH TYPE [").append(getType()).append("] VALUE [").append(getValue()).append("]");
|
||||
break;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Status for the response, from the 2 MSB of the value.
|
||||
*/
|
||||
private enum Status
|
||||
{
|
||||
ACK, //Success
|
||||
NACK, //Fail
|
||||
SACK, //Selective Retry
|
||||
RESERVED;
|
||||
|
||||
/**
|
||||
* Lookup the status from the value.
|
||||
* @param value of the status
|
||||
* @return lookup or RESERVED
|
||||
*/
|
||||
public static Status fromValue(int value)
|
||||
{
|
||||
int masked = (value & 0xC0) >> 6;
|
||||
|
||||
if(0 <= masked && masked <= 2)
|
||||
{
|
||||
return Status.values()[masked];
|
||||
}
|
||||
|
||||
return RESERVED;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.dmr.message.type;
|
||||
|
||||
/**
|
||||
* Known DMR gateways and system identities defined in ETSI TS 102 361-4 Table A.4 Tier III Gateways/Identifiers
|
||||
*/
|
||||
public enum Tier3Gateway
|
||||
{
|
||||
PSTNI(0xFFFEC0, "PSTN GATEWAY"),
|
||||
PABXI(0xFFFEC1, "PABX GATEWAY"),
|
||||
LINEI(0xFFFEC2, "LINE GATEWAY"),
|
||||
IPI(0xFFFEC3, "IP GATEWAY"),
|
||||
SUPLI(0xFFFEC4, "SUPPLEMENTARY DATA SERVICE"),
|
||||
SDMI(0xFFFEC5, "UDT SHORT DATA SERVICE"),
|
||||
REGI(0xFFFEC6, "REGISTRATION SERVICE"),
|
||||
MSI(0xFFFEC7, "CALL DIVERSION TO MS GATEWAY"),
|
||||
DIVERTI(0xFFFEC9, "CALL DIVERSION CANCELLATION"),
|
||||
TSI(0xFFFECA, "TRUNKING SYSTEM CONTROLLER"),
|
||||
DISPATI(0xFFFECB, "SYSTEM DISPATCHER"),
|
||||
STUNI(0xFFFECC, "MS STUN/REVIVE"),
|
||||
AUTHI(0xFFFECD, "AUTHENTICATION"),
|
||||
GPI(0xFFFECE, "CALL DIVERSION TO TALKGROUP GATEWAY"),
|
||||
KILLI(0xFFFECF, "MS KILL"),
|
||||
PSTNDI(0xFFFED0, "PSTN-D GATEWAY"),
|
||||
PABXDI(0xFFFED1, "PABX-D GATEWAY"),
|
||||
LINEDI(0xFFFED2, "LINE-D GATEWAY"),
|
||||
DISPATDI(0xFFFED3, "SYSTEM DISPATCHER-D"),
|
||||
ALLMSI(0xFFFED4, "ALL RADIOS/TALKGROUPS"),
|
||||
IPDI(0xFFFED5, "IP-D GATEWAY"),
|
||||
DGNAI(0xFFFED6, "DYNAMIC GROUP NUMBER ASSIGNMENT"),
|
||||
TATTSI(0xFFFED7, "TALKGROUP SUBSCRIBE/ATTACH SERVICE"),
|
||||
ALLMSIDL(0xFFFFFD, "ALL RADIOS AT SITE"),
|
||||
ALLMSIDZ(0xFFFFFE, "ALL RADIOS IN ZONE"),
|
||||
ALLMSID(0xFFFFFF, "ALL RADIOS IN SYSTEM"),
|
||||
RESERVED(0x0, "RESERVED");
|
||||
|
||||
private int mValue;
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param value of the identifier
|
||||
* @param label to describe the identifier
|
||||
*/
|
||||
Tier3Gateway(int value, String label)
|
||||
{
|
||||
mValue = value;
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the radio is a known gateway identifier
|
||||
* @param value to test
|
||||
* @return true if the value is a known gateway identifier
|
||||
*/
|
||||
public static boolean isGateway(int value)
|
||||
{
|
||||
if(value >= PSTNI.getValue())
|
||||
{
|
||||
for(Tier3Gateway gateway: Tier3Gateway.values())
|
||||
{
|
||||
if(gateway.matches(value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the DMR Tier-III gateway from a value.
|
||||
* @param value of the radio identifier
|
||||
* @return the known gateway or RESERVED if the value doesn't match a known gateway.
|
||||
*/
|
||||
public static Tier3Gateway fromValue(int value)
|
||||
{
|
||||
for(Tier3Gateway gateway: Tier3Gateway.values())
|
||||
{
|
||||
if(gateway.matches(value))
|
||||
{
|
||||
return gateway;
|
||||
}
|
||||
}
|
||||
|
||||
return RESERVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this entry matches the specified value.
|
||||
* @param value to test for match
|
||||
* @return true if the value matches.
|
||||
*/
|
||||
public boolean matches(int value)
|
||||
{
|
||||
return mValue == value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Numeric value for the entry.
|
||||
* @return value.
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print friendly label for the value.
|
||||
* @return label
|
||||
*/
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -51,7 +51,7 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
|
||||
public static final int COLUMN_FREQUENCY = 8;
|
||||
public static final int COLUMN_DETAILS = 9;
|
||||
|
||||
protected int mMaxMessages = 500;
|
||||
protected int mMaxMessages = 200;
|
||||
|
||||
protected List<IDecodeEvent> mEvents = new ArrayList<>();
|
||||
protected FilterSet<IDecodeEvent> mEventFilterSet = new DecodeEventFilterSet();
|
||||
|
||||
@@ -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.event;
|
||||
|
||||
@@ -26,13 +23,13 @@ import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.StuffBitsMessage;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.awt.EventQueue;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
public class MessageActivityModel extends AbstractTableModel implements Listener<IMessage>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -41,7 +38,7 @@ public class MessageActivityModel extends AbstractTableModel implements Listener
|
||||
private static final int TIMESLOT = 2;
|
||||
private static final int MESSAGE = 3;
|
||||
|
||||
protected int mMaxMessages = 500;
|
||||
protected int mMaxMessages = 200;
|
||||
protected LinkedList<MessageItem> mMessageItems = new LinkedList<>();
|
||||
protected int[] mColumnWidths = {20, 20, 500};
|
||||
protected String[] mHeaders = new String[]{"Time", "Protocol", "Timeslot", "Message"};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* 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
|
||||
@@ -15,22 +14,22 @@
|
||||
*
|
||||
* 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.ip;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
|
||||
import io.github.dsheirer.module.decode.ip.cellocator.MCGPHeader;
|
||||
import io.github.dsheirer.module.decode.ip.cellocator.MCGPMessageFactory;
|
||||
import io.github.dsheirer.module.decode.ip.icmp.ICMPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header;
|
||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
||||
import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
||||
import io.github.dsheirer.module.decode.ip.udp.UDPPort;
|
||||
import io.github.dsheirer.module.decode.ip.xcmp.XCMPPacket;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketHeader;
|
||||
import io.github.dsheirer.module.decode.p25.reference.IPHeaderCompression;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 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,14 +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.module.decode.ip;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -41,7 +39,7 @@ public class UnknownPacket implements IPacket
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("PACKET:");
|
||||
sb.append("UNKNOWN PACKET:");
|
||||
|
||||
if(getMessage().size() > getOffset())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Destination Identifier
|
||||
*/
|
||||
public class DestinationId extends HyteraToken
|
||||
{
|
||||
private static final int[] CONTENT_LENGTH = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int CONTENT_START = 32;
|
||||
private static final int[] ID = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
|
||||
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63};
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message containing both the token, run-length, and content
|
||||
*/
|
||||
public DestinationId(CorrectedBinaryMessage message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("DESTINATION:").append(getId());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the content field in bytes.
|
||||
* @return content field length.
|
||||
*/
|
||||
public int getContentLength()
|
||||
{
|
||||
return mMessage.getInt(CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify value parsed from the field content.
|
||||
*
|
||||
* Note: although this field has a content length specifier, I've hard-coded it to parse a 32-bit value for now
|
||||
* until we find need to make it more fancy.
|
||||
*/
|
||||
public int getId()
|
||||
{
|
||||
return mMessage.getInt(ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
HyteraTokenType getTokenType()
|
||||
{
|
||||
return HyteraTokenType.ID_DESTINATION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.HyteraEncodeFormat;
|
||||
|
||||
/**
|
||||
* Encoding
|
||||
*/
|
||||
public class Encoding extends HyteraToken
|
||||
{
|
||||
private static final int[] CONTENT_LENGTH = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int[] ENCODING = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message containing both the token, run-length, and content
|
||||
*/
|
||||
public Encoding(CorrectedBinaryMessage message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ENCODING FORMAT:").append(getEncoding());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the content field in bytes.
|
||||
* @return content field length.
|
||||
*/
|
||||
public int getContentLength()
|
||||
{
|
||||
return mMessage.getInt(CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hytera encoding format
|
||||
*/
|
||||
public HyteraEncodeFormat getEncoding()
|
||||
{
|
||||
return HyteraEncodeFormat.fromValue(getEncodingValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encoding lookup value.
|
||||
* @return value
|
||||
*/
|
||||
private int getEncodingValue()
|
||||
{
|
||||
return mMessage.getInt(ENCODING);
|
||||
}
|
||||
|
||||
@Override
|
||||
HyteraTokenType getTokenType()
|
||||
{
|
||||
return HyteraTokenType.ENCODING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Base abstract class for a field parsed from a Hytera Data Service message.
|
||||
*/
|
||||
public abstract class HyteraToken
|
||||
{
|
||||
protected CorrectedBinaryMessage mMessage;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param message containing both the token, run-length, and content
|
||||
*/
|
||||
public HyteraToken(CorrectedBinaryMessage message)
|
||||
{
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the token or field type for this field.
|
||||
*/
|
||||
abstract HyteraTokenType getTokenType();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Constructs Hytera token field instances.
|
||||
*/
|
||||
public class HyteraTokenFactory
|
||||
{
|
||||
/**
|
||||
* Creates a new parsed field instance
|
||||
* @param token that identifies the field type
|
||||
* @param message fragment that includes the token, run-length, and field content.
|
||||
* @return parsed field instance
|
||||
*/
|
||||
public static HyteraToken getField(HyteraTokenType token, CorrectedBinaryMessage message)
|
||||
{
|
||||
return switch(token)
|
||||
{
|
||||
case ID_DESTINATION -> new DestinationId(message);
|
||||
case ENCODING -> new Encoding(message);
|
||||
case ID_MESSAGE -> new MessageId(message);
|
||||
case PAYLOAD -> new Payload(message);
|
||||
case MESSAGE_HEADER -> new MessageHeader(message);
|
||||
case ID_SOURCE -> new SourceId(message);
|
||||
default -> new Unknown(message);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ip.hytera.sds;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.ip.IHeader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Message parser/decoder for Hytera run-length encoded payload message carried in Proprietary Data packet sequences.
|
||||
*/
|
||||
public class HyteraTokenHeader implements IHeader
|
||||
{
|
||||
private static int[] TWO_BYTES = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
private static int[] FOUR_BYTES = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
|
||||
private CorrectedBinaryMessage mMessage;
|
||||
private List<HyteraToken> mParsedTokens = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param message assembled from data blocks.
|
||||
*/
|
||||
public HyteraTokenHeader(CorrectedBinaryMessage message)
|
||||
{
|
||||
mMessage = message;
|
||||
parse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("HYTERA TOKEN HEADER: ").append(Joiner.on(",").join(getParsedTokens()));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this header contains enough tokens to create an SMS message
|
||||
*/
|
||||
public boolean isSMSMessage()
|
||||
{
|
||||
return hasTokens(HyteraTokenType.MESSAGE_HEADER, HyteraTokenType.ENCODING, HyteraTokenType.PAYLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message has all of the specified tokens.
|
||||
* @param types one or more tokens.
|
||||
* @return true if all tokens are present in this payload.
|
||||
*/
|
||||
public boolean hasTokens(HyteraTokenType ... types)
|
||||
{
|
||||
boolean hasToken = true;
|
||||
|
||||
for(HyteraTokenType type: types)
|
||||
{
|
||||
hasToken &= hasToken(type);
|
||||
}
|
||||
|
||||
return hasToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this payload has the specified token type.
|
||||
* @param type of token to check
|
||||
* @return true if this payload has a non-null instance of the token type
|
||||
*/
|
||||
public boolean hasToken(HyteraTokenType type)
|
||||
{
|
||||
return getTokenByType(type) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves tokens by type.
|
||||
* @param type of token
|
||||
* @return token or null
|
||||
*/
|
||||
public HyteraToken getTokenByType(HyteraTokenType type)
|
||||
{
|
||||
for(HyteraToken token: mParsedTokens)
|
||||
{
|
||||
if(token.getTokenType() == type)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields parsed from this SDS message
|
||||
* @return fields
|
||||
*/
|
||||
public List<HyteraToken> getParsedTokens()
|
||||
{
|
||||
return mParsedTokens;
|
||||
}
|
||||
|
||||
private void parse()
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
while(offset < mMessage.length())
|
||||
{
|
||||
int opcode = mMessage.getInt(TWO_BYTES, offset);
|
||||
int length = 2 * 8; //Opcode length in bits
|
||||
|
||||
HyteraTokenType token = HyteraTokenType.fromOpcode(opcode);
|
||||
|
||||
if(token != HyteraTokenType.UNKNOWN)
|
||||
{
|
||||
if(token.isFixedLength())
|
||||
{
|
||||
length += token.getFieldLength() * 8; //Content length
|
||||
}
|
||||
else
|
||||
{
|
||||
int contentLength = mMessage.getInt(TWO_BYTES, offset + length);
|
||||
length += 2 * 8; //Content length 4 bytes
|
||||
length += contentLength * 8; //Actual content
|
||||
}
|
||||
|
||||
HyteraToken field = HyteraTokenFactory.getField(token, mMessage.getSubMessage(offset, offset + length + 1));
|
||||
offset += length;
|
||||
mParsedTokens.add(field);
|
||||
}
|
||||
else
|
||||
{
|
||||
CorrectedBinaryMessage remainder = mMessage.getSubMessage(offset, mMessage.length() + 1);
|
||||
HyteraToken unknown = HyteraTokenFactory.getField(HyteraTokenType.UNKNOWN, remainder);
|
||||
mParsedTokens.add(unknown);
|
||||
offset = mMessage.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ip.hytera.sds;
|
||||
|
||||
/**
|
||||
* Hytera run-length encoding tokens.
|
||||
*
|
||||
* Each token implies a parseable field that is optionally followed by a 4-byte field content length and then followed
|
||||
* by the contents of the field.
|
||||
*
|
||||
* In the enum entries below, a field length of -1 indicates the opcode is followed by a 4-byte content length value.
|
||||
*/
|
||||
public enum HyteraTokenType
|
||||
{
|
||||
//Note: this may be the message type, but is definitely followed by 4-byte total length field. Need more examples
|
||||
MESSAGE_HEADER(0x0201, 4), //First 2 bytes are total message length, 2nd 2 bytes = unknown
|
||||
ID_MESSAGE(0x0001, -1),
|
||||
ID_SOURCE(0x0002, -1),
|
||||
ID_DESTINATION(0x0003, -1),
|
||||
ENCODING(0x0101, -1),
|
||||
PAYLOAD(0x0102, -1),
|
||||
UNKNOWN(-1, -1);
|
||||
|
||||
private int mOpcode;
|
||||
private int mFieldLength;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param opcode value
|
||||
* @param fieldLength in bytes, or -1 to indicate the opcode is followed by a 4-byte run length value.
|
||||
*/
|
||||
HyteraTokenType(int opcode, int fieldLength)
|
||||
{
|
||||
mOpcode = opcode;
|
||||
mFieldLength = fieldLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opcode value
|
||||
* @return integer value of the opcode
|
||||
*/
|
||||
public int getOpcode()
|
||||
{
|
||||
return mOpcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the field content in bytes that follow the opcode. When this value is a -1, then the opcode is
|
||||
* followed by a 4-byte value that indicates the length of the content field that follows immediately after.
|
||||
* @return field length
|
||||
*/
|
||||
public int getFieldLength()
|
||||
{
|
||||
return mFieldLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a fixed length field.
|
||||
*/
|
||||
public boolean isFixedLength()
|
||||
{
|
||||
return mFieldLength >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a token from the opcode value.
|
||||
* @param opcode value to lookup
|
||||
* @return token or UNKNOWN
|
||||
*/
|
||||
public static HyteraTokenType fromOpcode(int opcode)
|
||||
{
|
||||
for(HyteraTokenType token: HyteraTokenType.values())
|
||||
{
|
||||
if(token.getOpcode() == opcode)
|
||||
{
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hytera SDS Long Data Message with unknown encoded payload
|
||||
*/
|
||||
public class HyteraUnknownPacket implements IPacket
|
||||
{
|
||||
private HyteraTokenHeader mHeader;
|
||||
|
||||
/**
|
||||
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||
*
|
||||
* @param header to the packet within the message
|
||||
*/
|
||||
public HyteraUnknownPacket(HyteraTokenHeader header)
|
||||
{
|
||||
mHeader = header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("HYTERA UNKNOWN LONG DATA SERVICE TOKEN MESSAGE:").append(getHeader().toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HyteraTokenHeader getHeader()
|
||||
{
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPacket getPayload() {return null;}
|
||||
@Override
|
||||
public boolean hasPayload() {return false;}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ip.hytera.sds;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Message Header
|
||||
*
|
||||
* Initial token that specifies the overall message length and some other unidentified bit fields.
|
||||
*
|
||||
* Note: I've only confirmed this is the start token for long SMS messages, but it may also be the start token for
|
||||
* other packet data messages like GPS or any other packet overlay application.
|
||||
*/
|
||||
public class MessageHeader extends HyteraToken
|
||||
{
|
||||
private static int[] MESSAGE_LENGTH = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static int[] MESSAGE_FLAGS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param message containing both the token, run-length, and content
|
||||
*/
|
||||
public MessageHeader(CorrectedBinaryMessage message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("MESSAGE HEADER LENGTH:").append(getMessageLength());
|
||||
sb.append(" FLAGS:").append(getFlags());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
HyteraTokenType getTokenType()
|
||||
{
|
||||
return HyteraTokenType.MESSAGE_HEADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Likely a set of bit fields that indicate optional flags for the message like:
|
||||
* - Acknowledge Receipt (boolean)
|
||||
* - Priority/Flash (boolean)
|
||||
* - Encryption Type (enum of at least 4 values, ie 2+ bits)
|
||||
* - Emergency (boolean)
|
||||
* @return
|
||||
*/
|
||||
public String getFlags()
|
||||
{
|
||||
return mMessage.getHex(MESSAGE_FLAGS, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overall message length in bytes
|
||||
* @return message length.
|
||||
*/
|
||||
public int getMessageLength()
|
||||
{
|
||||
return mMessage.getInt(MESSAGE_LENGTH);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user