mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Compare commits
1 Commits
master
...
697-hackrf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c05fd33ca6 |
264
build.gradle
264
build.gradle
@@ -95,53 +95,66 @@ test {
|
|||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
// JUnit Tests
|
// JUnit Tests
|
||||||
testImplementation(platform("org.junit:junit-bom:6.1.3"))
|
testImplementation(platform("org.junit:junit-bom:6.0.1"))
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.1'
|
||||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1")
|
||||||
|
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:6.0.1")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|
||||||
//Jitpack imports
|
//Jitpack imports
|
||||||
implementation 'com.github.dnault:libresample4j:master'
|
implementation 'com.github.dnault:libresample4j:master'
|
||||||
implementation 'com.github.DSheirer.jmbe:api:v2.1.1'
|
implementation 'com.github.DSheirer.jmbe:api:v2.1.0'
|
||||||
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
implementation 'com.github.nwaldispuehl:java-lame:v3.98.4'
|
||||||
|
|
||||||
//mavenCentral/jcenter imports
|
//mavenCentral/jcenter imports
|
||||||
implementation 'ch.qos.logback:logback-classic:1.6.6'
|
implementation 'ch.qos.logback:logback-classic:1.6.3'
|
||||||
implementation 'ch.qos.logback:logback-core:1.6.6'
|
implementation 'ch.qos.logback:logback-core:1.5.38'
|
||||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.23.0'
|
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.22.4'
|
||||||
implementation 'com.formdev:flatlaf:3.8'
|
implementation 'com.fazecast:jSerialComm:2.11.4'
|
||||||
implementation 'com.formdev:flatlaf-intellij-themes:3.8' //Not imported - Theme.java loads these themes by class name
|
implementation 'com.formdev:flatlaf:3.7.2'
|
||||||
|
implementation 'com.formdev:flatlaf-intellij-themes:3.7.2'
|
||||||
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
||||||
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
|
implementation 'com.github.jiconfont:jiconfont-javafx:1.0.0'
|
||||||
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
implementation 'com.github.jiconfont:jiconfont-swing:1.0.1'
|
||||||
implementation('com.github.wendykierp:JTransforms:3.2') {
|
implementation 'com.github.wendykierp:JTransforms:3.2'
|
||||||
exclude group: 'junit' //JLargeArrays 1.7 declares junit as a compile dependency
|
implementation 'com.google.code.gson:gson:2.14.0'
|
||||||
}
|
implementation 'com.google.guava:guava:33.6.0-jre'
|
||||||
implementation 'com.google.code.gson:gson:2.15.0'
|
|
||||||
implementation 'com.google.guava:guava:33.8.0-jre'
|
|
||||||
implementation 'com.jidesoft:jide-oss:3.6.18'
|
implementation 'com.jidesoft:jide-oss:3.6.18'
|
||||||
implementation 'com.miglayout:miglayout-swing:11.5.2'
|
implementation 'com.miglayout:miglayout-swing:11.4.3'
|
||||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||||
implementation 'commons-io:commons-io:2.22.0'
|
implementation 'commons-io:commons-io:2.22.0'
|
||||||
implementation 'io.github.dsheirer:jairspy:1.0.12.1' //Airspy tuners - GitHub release, see settings.gradle
|
implementation 'eu.hansolo:charts:1.0.5'
|
||||||
implementation 'io.github.dsheirer:jairspyhf:1.8.1.1' //Airspy HF+ tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jairspy:1.0.12' //Airspy tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:jbladerf:2.6.0.1' //bladeRF tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jairspyhf:1.8.1' //Airspy HF+ tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:jhackrf:2026.01.3.1' //HackRF tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:jlibusb:1.0.30.1' //libusb and USB hotplug for the tuner libraries - GitHub release; not pulled in transitively
|
implementation 'io.github.dsheirer:jhackrf:2026.01.3' //HackRF tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:jhydrasdr:1.1.4.1' //HydraSDR tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jhydrasdr:1.1.4' //HydraSDR tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:jlimesdr:25.1.0.2' //LimeSDR tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jrtl:1.4.0.1' //RTL-SDR tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:jrtl:1.4.0.3' //RTL-SDR tuners - GitHub release, see settings.gradle
|
|
||||||
implementation 'io.github.dsheirer:jsdrplay:3.15.3' //SDRplay RSP tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jsdrplay:3.15.3' //SDRplay RSP tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
||||||
implementation 'org.apache.commons:commons-compress:1.28.0' //Not used directly - overrides the vulnerable 1.21 version from jarchivelib
|
implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs
|
||||||
implementation 'org.apache.commons:commons-lang3:3.21.0'
|
implementation('io.github.dsheirer:usb4java:1.3.5') { //Fork with Loader support for OSX Multi-Version
|
||||||
|
//Excludes the javax.usb API and the duplicate original usb4java classes. The native libraries are listed below.
|
||||||
|
exclude group: 'org.usb4java', module: 'usb4java-javax'
|
||||||
|
}
|
||||||
|
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||||
|
implementation 'org.apache.commons:commons-compress:1.28.0'
|
||||||
|
implementation 'org.apache.commons:commons-lang3:3.20.0'
|
||||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||||
implementation 'org.apache.commons:commons-csv:1.14.1'
|
implementation 'org.apache.commons:commons-csv:1.14.1'
|
||||||
implementation 'org.apache.mina:mina-core:2.2.10'
|
implementation 'org.apache.mina:mina-core:2.2.9'
|
||||||
implementation 'org.apache.mina:mina-http:2.2.10'
|
implementation 'org.apache.mina:mina-http:2.2.9'
|
||||||
implementation 'org.controlsfx:controlsfx:11.2.6'
|
implementation 'org.controlsfx:controlsfx:11.2.3'
|
||||||
implementation 'org.rauschig:jarchivelib:1.2.0'
|
implementation 'org.rauschig:jarchivelib:1.2.0'
|
||||||
implementation 'org.slf4j:slf4j-api:2.0.20'
|
implementation 'org.slf4j:slf4j-api:2.0.17'
|
||||||
|
implementation 'org.usb4java:libusb4java:1.3.0:darwin-x86-64' //usb4java native library
|
||||||
|
implementation 'org.usb4java:libusb4java:1.3.0:linux-aarch64' //usb4java native library
|
||||||
|
implementation 'org.usb4java:libusb4java:1.3.0:linux-arm' //usb4java native library
|
||||||
|
implementation 'org.usb4java:libusb4java:1.3.0:linux-x86' //usb4java native library
|
||||||
|
implementation 'org.usb4java:libusb4java:1.3.0:linux-x86-64' //usb4java native library
|
||||||
|
implementation 'org.usb4java:libusb4java:1.3.0:win32-x86' //usb4java native library
|
||||||
|
implementation 'org.usb4java:libusb4java:1.3.0:win32-x86-64' //usb4java native library
|
||||||
|
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||||
}
|
}
|
||||||
|
|
||||||
def os = DefaultNativePlatform.currentOperatingSystem
|
def os = DefaultNativePlatform.currentOperatingSystem
|
||||||
@@ -232,6 +245,18 @@ String targetOsxX86_64 = 'osx-x86_64-v' + version
|
|||||||
String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
||||||
String targetWindowsAarch64 = 'windows-aarch64-v' + version
|
String targetWindowsAarch64 = 'windows-aarch64-v' + version
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional pre-downloaded Java Development Kit (JDK) for each target OS and CPU. These locations are hard-coded to the
|
||||||
|
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
||||||
|
*/
|
||||||
|
String jdk_base = '/home/denny/java_jdks/'
|
||||||
|
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-25.0.1-full'
|
||||||
|
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-25.0.1-full'
|
||||||
|
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-25.0.1-full.jdk'
|
||||||
|
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-25.0.1-full.jdk'
|
||||||
|
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-25.0.1-full'
|
||||||
|
String jdk_windows_aarch64 = jdk_base + 'windows-arm64/jdk-25.0.1-full'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download URLs to download the JDK as part of the gradle build packaging process
|
* Download URLs to download the JDK as part of the gradle build packaging process
|
||||||
*/
|
*/
|
||||||
@@ -260,138 +285,19 @@ def configure(RuntimeZipTask rt, List<String> jvmArgs) {
|
|||||||
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
|
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
|
||||||
//jdk.accessibility is used with assistive technologies like screen readers
|
//jdk.accessibility is used with assistive technologies like screen readers
|
||||||
//java.management for JVM resource monitoring
|
//java.management for JVM resource monitoring
|
||||||
//java.base, java.logging, java.net.http, java.prefs, javafx.controls, javafx.swing - required by sdrtrunk (jdeps)
|
rt.extension.addModules('jdk.charsets', 'jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management')
|
||||||
rt.extension.addModules('jdk.charsets', 'jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management',
|
|
||||||
'java.base', 'java.logging', 'java.net.http', 'java.prefs', 'javafx.controls', 'javafx.swing')
|
|
||||||
|
|
||||||
//Use only the modules listed above. The runtime plugin's module auto-detection uses a bundled ASM library that
|
//Use auto-detected modules and 'add' any specified modules.
|
||||||
//can't read Java 27 class files and logs a 'Failed to scan' error for each sdrtrunk class. The jdeps module check
|
rt.extension.additive.set(true)
|
||||||
//on the runtime task verifies that this list is complete.
|
|
||||||
rt.extension.additive.set(false)
|
|
||||||
|
|
||||||
//Recommended options for reducing/shrinking runtime target image size
|
//Recommended options for reducing/shrinking runtime target image size
|
||||||
rt.extension.addOptions('--strip-debug', '--compress', 'zip-6', '--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.
|
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
||||||
// rt.extension.imageDir=layout.buildDirectory.dir('image')
|
// rt.extension.imageDir=layout.buildDirectory.dir('image')
|
||||||
rt.extension.imageZip.set(file(layout.buildDirectory.dir('image').get().file('sdr-trunk.zip')))
|
rt.extension.imageZip.set(file(layout.buildDirectory.dir('image').get().file('sdr-trunk.zip')))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rewrites the jar file without the native library folders for other operating systems and architectures. Fails the
|
|
||||||
* build if a jar that contains native libraries doesn't contain the folder for the target.
|
|
||||||
* @param jar file to rewrite
|
|
||||||
* @param keep native folder for the target
|
|
||||||
* @return number of bytes removed from the jar
|
|
||||||
*/
|
|
||||||
long stripJar(File jar, String keep) {
|
|
||||||
def tunerPattern = ~/^[^\/]+\/native\/([^\/]+)\/.*/
|
|
||||||
def removed = [] as Set
|
|
||||||
def hadTuner = false, keptTuner = false
|
|
||||||
File temp = new File(jar.parentFile, jar.name + '.tmp')
|
|
||||||
|
|
||||||
new java.util.zip.ZipFile(jar).withCloseable { zip ->
|
|
||||||
def entries = zip.entries().toList()
|
|
||||||
|
|
||||||
entries.each { e ->
|
|
||||||
def tuner = tunerPattern.matcher(e.name)
|
|
||||||
|
|
||||||
if(tuner.matches()) {
|
|
||||||
hadTuner = true
|
|
||||||
if(tuner.group(1) == keep) { keptTuner = true } else { removed << e.name }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hadTuner && !keptTuner) {
|
|
||||||
throw new GradleException("Native strip: ${jar.name} has no native/${keep} folder")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!removed.isEmpty()) {
|
|
||||||
new java.util.zip.ZipOutputStream(new FileOutputStream(temp)).withCloseable { out ->
|
|
||||||
entries.findAll { !removed.contains(it.name) }.each { e ->
|
|
||||||
def copy = new java.util.zip.ZipEntry(e)
|
|
||||||
if(copy.method == java.util.zip.ZipEntry.DEFLATED) {
|
|
||||||
copy.compressedSize = -1
|
|
||||||
}
|
|
||||||
out.putNextEntry(copy)
|
|
||||||
zip.getInputStream(e).withCloseable { out << it }
|
|
||||||
out.closeEntry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(removed.isEmpty()) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
long before = jar.length()
|
|
||||||
java.nio.file.Files.move(temp.toPath(), jar.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING)
|
|
||||||
return before - jar.length()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the native libraries for other operating systems and architectures from the jars in each runtime target
|
|
||||||
* image, before the runtimeZip tasks package the images. The tuner libraries (jRTL, jAirspy, etc.) package their
|
|
||||||
* native libraries under '<library>/native/<os>-<arch>/', where the folder name matches the runtime target name
|
|
||||||
* '<os>-<arch>-v<version>' without the version suffix, except that the libraries use 'macos' where the target uses 'osx'.
|
|
||||||
*/
|
|
||||||
tasks.named('runtime') {rt ->
|
|
||||||
rt.doLast {
|
|
||||||
rt.targetPlatforms.get().keySet().each { target ->
|
|
||||||
def nativeFolder = (target - "-v${version}").replaceFirst(/^osx-/, 'macos-')
|
|
||||||
def lib = new File(rt.imageDir.asFile, "${project.name}-${target}/lib")
|
|
||||||
long total = 0
|
|
||||||
lib.listFiles({ File f -> f.name.endsWith('.jar') } as FileFilter).each { jar ->
|
|
||||||
total += stripJar(jar, nativeFolder)
|
|
||||||
}
|
|
||||||
logger.lifecycle("Native strip: removed ${String.format('%.1f', total / 1048576)} MB of other " +
|
|
||||||
"platform native libraries from [${target}]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies that each runtime target image contains every module that the sdrtrunk jar requires, as reported by jdeps.
|
|
||||||
* This guards against the runtime plugin's module auto-detection silently missing modules (e.g. when it can't parse
|
|
||||||
* class files compiled for a newer Java version).
|
|
||||||
*/
|
|
||||||
def jdepsHome = javaToolchains.launcherFor(java.toolchain).map { it.metadata.installationPath.asFile }
|
|
||||||
def mainJar = tasks.named('jar').flatMap { it.archiveFile }
|
|
||||||
|
|
||||||
tasks.named('runtime') {rt ->
|
|
||||||
rt.doLast {
|
|
||||||
def jdeps = new File(jdepsHome.get(), 'bin/' + (os.isWindows() ? 'jdeps.exe' : 'jdeps')).absolutePath
|
|
||||||
def process = new ProcessBuilder(jdeps, '--multi-release', java.toolchain.languageVersion.get().toString(),
|
|
||||||
'--ignore-missing-deps', '--print-module-deps', '-q', mainJar.get().asFile.absolutePath)
|
|
||||||
.redirectErrorStream(true).start()
|
|
||||||
def output = process.inputStream.text.trim()
|
|
||||||
|
|
||||||
if(process.waitFor() != 0) {
|
|
||||||
throw new GradleException("Module check: jdeps failed: ${output}")
|
|
||||||
}
|
|
||||||
|
|
||||||
def required = output.split(',')*.trim().findAll { it } as Set
|
|
||||||
|
|
||||||
rt.targetPlatforms.get().keySet().each { target ->
|
|
||||||
def release = new File(rt.imageDir.asFile, "${project.name}-${target}/release")
|
|
||||||
def match = release.text =~ /(?m)^MODULES="([^"]*)"/
|
|
||||||
|
|
||||||
if(!match.find()) {
|
|
||||||
throw new GradleException("Module check: no MODULES entry in ${release}")
|
|
||||||
}
|
|
||||||
|
|
||||||
def missing = required - (match.group(1).split(' ') as Set)
|
|
||||||
|
|
||||||
if(missing) {
|
|
||||||
throw new GradleException("Module check: runtime image [${target}] is missing required modules " +
|
|
||||||
"${missing.sort()} - add them to addModules() in build.gradle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.lifecycle("Module check: runtime images contain all modules required by sdrtrunk: ${required.sort()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a release package for the current operating system.
|
* Creates a release package for the current operating system.
|
||||||
*
|
*
|
||||||
@@ -448,12 +354,22 @@ tasks.register('runtimeZipCurrent', RuntimeZipTask) {rt ->
|
|||||||
*/
|
*/
|
||||||
tasks.register('runtimeZipWindows', RuntimeZipTask) {rt ->
|
tasks.register('runtimeZipWindows', RuntimeZipTask) {rt ->
|
||||||
//Windows aarch64
|
//Windows aarch64
|
||||||
rt.extension.targetPlatform(targetWindowsAarch64) {
|
if(file(jdk_windows_aarch64).exists()) {
|
||||||
jdkHome = jdkDownload(jdk_download_windows_aarch64)
|
rt.extension.targetPlatform(targetWindowsAarch64, jdk_windows_aarch64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetWindowsAarch64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_windows_aarch64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//Windows x86-64
|
//Windows x86-64
|
||||||
rt.extension.targetPlatform(targetWindowsX86_64) {
|
if(file(jdk_windows_x86_64).exists()) {
|
||||||
jdkHome = jdkDownload(jdk_download_windows_x86_64)
|
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)
|
configure(rt, jvmArgsWindows)
|
||||||
@@ -470,20 +386,40 @@ tasks.register('runtimeZipWindows', RuntimeZipTask) {rt ->
|
|||||||
*/
|
*/
|
||||||
tasks.register('runtimeZipOthers', RuntimeZipTask) {rt ->
|
tasks.register('runtimeZipOthers', RuntimeZipTask) {rt ->
|
||||||
//Linux aarch64
|
//Linux aarch64
|
||||||
rt.extension.targetPlatform(targetLinuxAarch64) {
|
if(file(jdk_linux_aarch64).exists()) {
|
||||||
jdkHome = jdkDownload(jdk_download_linux_aarch64)
|
rt.extension.targetPlatform(targetLinuxAarch64, jdk_linux_aarch64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetLinuxAarch64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_linux_aarch64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//Linux x86-64
|
//Linux x86-64
|
||||||
rt.extension.targetPlatform(targetLinuxX86_64) {
|
if(file(jdk_linux_x86_64).exists()) {
|
||||||
jdkHome = jdkDownload(jdk_download_linux_x86_64)
|
rt.extension.targetPlatform(targetLinuxX86_64, jdk_linux_x86_64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetLinuxX86_64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_linux_x86_64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//OSX aarch64
|
//OSX aarch64
|
||||||
rt.extension.targetPlatform(targetOsxAarch64) {
|
if(file(jdk_osx_aarch64).exists()) {
|
||||||
jdkHome = jdkDownload(jdk_download_osx_aarch64)
|
rt.extension.targetPlatform(targetOsxAarch64, jdk_osx_aarch64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetOsxAarch64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_osx_aarch64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//OSX x86-64
|
//OSX x86-64
|
||||||
rt.extension.targetPlatform(targetOsxX86_64) {
|
if(file(jdk_osx_x86_64).exists()) {
|
||||||
jdkHome = jdkDownload(jdk_download_osx_x86_64)
|
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)
|
configure(rt, jvmArgsLinux)
|
||||||
|
|||||||
@@ -28,4 +28,4 @@
|
|||||||
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
||||||
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
||||||
|
|
||||||
projectVersion=1.0.0-beta-1
|
projectVersion=0.6.2-beta-1
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.9.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
retries=0
|
retries=0
|
||||||
retryBackOffMs=500
|
retryBackOffMs=500
|
||||||
|
|||||||
52
gradlew.bat
vendored
52
gradlew.bat
vendored
@@ -26,33 +26,6 @@
|
|||||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
setlocal EnableExtensions
|
setlocal EnableExtensions
|
||||||
|
|
||||||
@rem Catch executions from older scripts and ensure they exit cleanly.
|
|
||||||
@rem This can be removed once we can be reasonably confident that few people
|
|
||||||
@rem will be migrating directly to this new wrapper.
|
|
||||||
goto afterSafetyNet
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
||||||
goto exitWithErrorLevel
|
|
||||||
:afterSafetyNet
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
@rem This is normally unused
|
@rem This is normally unused
|
||||||
@@ -72,14 +45,13 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
1>&2 echo.
|
echo. 1>&2
|
||||||
1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
1>&2 echo.
|
echo. 1>&2
|
||||||
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
1>&2 echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
"%COMSPEC%" /c exit 1
|
"%COMSPEC%" /c exit 1
|
||||||
goto exitWithErrorLevel
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -87,14 +59,13 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
1>&2 echo.
|
echo. 1>&2
|
||||||
1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
1>&2 echo.
|
echo. 1>&2
|
||||||
1>&2 echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
1>&2 echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
"%COMSPEC%" /c exit 1
|
"%COMSPEC%" /c exit 1
|
||||||
goto exitWithErrorLevel
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
@@ -104,9 +75,8 @@ goto exitWithErrorLevel
|
|||||||
@rem Execute gradlew
|
@rem Execute gradlew
|
||||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
@rem which allows us to clear the local environment before executing the java command
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
@rem This label must not be changed. We rely on old scripts being able to jump to this point.
|
|
||||||
:exitWithErrorLevel
|
:exitWithErrorLevel
|
||||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
|
|||||||
1023
sdr-trunk.ipr
Normal file
1023
sdr-trunk.ipr
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ dependencyResolutionManagement {
|
|||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
|
|
||||||
//jAirspy is published as a GitHub release, with the native libraries for all platforms in the jar
|
//jAirspy is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||||
ivy {
|
ivy {
|
||||||
@@ -33,15 +33,6 @@ dependencyResolutionManagement {
|
|||||||
content { includeModule 'io.github.dsheirer', 'jrtl' }
|
content { includeModule 'io.github.dsheirer', 'jrtl' }
|
||||||
}
|
}
|
||||||
|
|
||||||
//jlibusb is published as a GitHub release, with libusb for all platforms in the jar. Tuner libraries built on
|
|
||||||
//jlibusb (jAirspy, jAirspyHF, jBladeRF, jHackRF, jHydraSDR, jLimeSDR, jRTL) load their native library next to its libusb, so the process shares one copy.
|
|
||||||
ivy {
|
|
||||||
url = 'https://github.com/DSheirer/jlibusb/releases/download'
|
|
||||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
|
||||||
metadataSources { artifact() }
|
|
||||||
content { includeModule 'io.github.dsheirer', 'jlibusb' }
|
|
||||||
}
|
|
||||||
|
|
||||||
//jHackRF is published as a GitHub release, with the native libraries for all platforms in the jar
|
//jHackRF is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||||
ivy {
|
ivy {
|
||||||
url = 'https://github.com/DSheirer/jHackRF/releases/download'
|
url = 'https://github.com/DSheirer/jHackRF/releases/download'
|
||||||
@@ -58,14 +49,6 @@ dependencyResolutionManagement {
|
|||||||
content { includeModule 'io.github.dsheirer', 'jhydrasdr' }
|
content { includeModule 'io.github.dsheirer', 'jhydrasdr' }
|
||||||
}
|
}
|
||||||
|
|
||||||
//jLimeSDR is published as a GitHub release, with the native libraries for all platforms in the jar
|
|
||||||
ivy {
|
|
||||||
url = 'https://github.com/DSheirer/jLimeSDR/releases/download'
|
|
||||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
|
||||||
metadataSources { artifact() }
|
|
||||||
content { includeModule 'io.github.dsheirer', 'jlimesdr' }
|
|
||||||
}
|
|
||||||
|
|
||||||
//jsdrplay is published as a GitHub release. The jar does not contain the SDRplay API native library, which
|
//jsdrplay is published as a GitHub release. The jar does not contain the SDRplay API native library, which
|
||||||
//users install separately from sdrplay.com
|
//users install separately from sdrplay.com
|
||||||
ivy {
|
ivy {
|
||||||
|
|||||||
@@ -0,0 +1,483 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.audio.convert.thumbdv;
|
||||||
|
|
||||||
|
import com.fazecast.jSerialComm.SerialPort;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessageFactory;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.request.AmbeRequest;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.request.DecodeSpeechRequest;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.request.EncodeSpeechRequest;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.request.ResetRequest;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.request.SetVocoderParametersRequest;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.request.SetVocoderRequest;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Northwest Digital Radio (NWDR) ThumbDv dongle.
|
||||||
|
*
|
||||||
|
* Note: linux users must allow access to the serial port:
|
||||||
|
*
|
||||||
|
* sudo usermod -a -G uucp username
|
||||||
|
* sudo usermod -a -G dialout username
|
||||||
|
* sudo usermod -a -G lock username
|
||||||
|
* sudo usermod -a -G tty username
|
||||||
|
*/
|
||||||
|
public class ThumbDv implements AutoCloseable
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(ThumbDv.class);
|
||||||
|
private static final String PORT_DESCRIPTION = "USB-to-Serial Port (ftdi_sio)";
|
||||||
|
private static final String PORT_DESCRIPTION_FRAGMENT = "USB Serial Port";
|
||||||
|
private static final int BAUD_RATE = 460800;
|
||||||
|
private static final byte PACKET_START = (byte) 0x61;
|
||||||
|
|
||||||
|
public enum AudioProtocol
|
||||||
|
{
|
||||||
|
DMR,
|
||||||
|
DSTAR,
|
||||||
|
NXDN,
|
||||||
|
P25_PHASE2,
|
||||||
|
}
|
||||||
|
|
||||||
|
private SerialPort mSerialPort;
|
||||||
|
private ScheduledFuture mSerialPortReaderHandle;
|
||||||
|
private ScheduledFuture mAudioDecodeProcessorHandle;
|
||||||
|
private LinkedTransferQueue<DecodeSpeechRequest> mDecodeSpeechRequests = new LinkedTransferQueue<>();
|
||||||
|
private AudioProtocol mAudioProtocol;
|
||||||
|
private Listener<float[]> mAudioBufferListener;
|
||||||
|
private boolean mStarted;
|
||||||
|
|
||||||
|
public ThumbDv(AudioProtocol audioProtocol, Listener<float[]> listener)
|
||||||
|
{
|
||||||
|
mAudioProtocol = audioProtocol;
|
||||||
|
mAudioBufferListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueues the audio code frame for decoding. Decoded PCM speech packet will be sent to the registered
|
||||||
|
* audio packet listener.
|
||||||
|
*
|
||||||
|
* @param codecFrame
|
||||||
|
*/
|
||||||
|
public void decode(byte[] codecFrame)
|
||||||
|
{
|
||||||
|
if(!mStarted || mSerialPort == null)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Must invoke start() method and thumbdv serial device must be available");
|
||||||
|
}
|
||||||
|
|
||||||
|
mDecodeSpeechRequests.offer(new DecodeSpeechRequest(codecFrame));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
if(mSerialPortReaderHandle != null)
|
||||||
|
{
|
||||||
|
mSerialPortReaderHandle.cancel(true);
|
||||||
|
mSerialPortReaderHandle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mSerialPort != null)
|
||||||
|
{
|
||||||
|
mSerialPort.closePort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws IOException
|
||||||
|
{
|
||||||
|
if(mSerialPort == null)
|
||||||
|
{
|
||||||
|
SerialPort[] ports = SerialPort.getCommPorts();
|
||||||
|
|
||||||
|
for(SerialPort port : ports)
|
||||||
|
{
|
||||||
|
mLog.debug("Serial Port Name:" + port.getDescriptivePortName());
|
||||||
|
if(port.getDescriptivePortName().contentEquals(PORT_DESCRIPTION) ||
|
||||||
|
port.getDescriptivePortName().contains(PORT_DESCRIPTION_FRAGMENT))
|
||||||
|
{
|
||||||
|
mSerialPort = port;
|
||||||
|
mSerialPort.setBaudRate(BAUD_RATE);
|
||||||
|
mSerialPort.openPort(0, 5000, 10000);
|
||||||
|
|
||||||
|
if(mSerialPort.isOpen())
|
||||||
|
{
|
||||||
|
mLog.info("Resetting ThumbDv Device");
|
||||||
|
send(new ResetRequest());
|
||||||
|
|
||||||
|
mLog.info("Creating Serial Port Reader");
|
||||||
|
final Runnable r = new SerialPortReader(mSerialPort.getInputStream());
|
||||||
|
mLog.info("Starting Serial Port Reader");
|
||||||
|
mSerialPortReaderHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(r, 0,
|
||||||
|
5, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
mStarted = true;
|
||||||
|
mLog.info("Startup complete - awaiting reset response");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.warn("Could not open serial port: " + mSerialPort.getSystemPortName());
|
||||||
|
throw new IOException("Could not open serial port:" + mSerialPort.getSystemPortName());
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mSerialPort == null)
|
||||||
|
{
|
||||||
|
throw new IOException("ThumbDV serial port not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(byte[] message) throws IOException
|
||||||
|
{
|
||||||
|
if(mSerialPort == null)
|
||||||
|
{
|
||||||
|
throw new IOException("ThumbDv must be started before use");
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytesWritten = mSerialPort.writeBytes(message, message.length);
|
||||||
|
|
||||||
|
if(bytesWritten < 0)
|
||||||
|
{
|
||||||
|
throw new IOException("Unable to write message:" + Arrays.toString(message));
|
||||||
|
}
|
||||||
|
else if(bytesWritten != message.length)
|
||||||
|
{
|
||||||
|
throw new IOException("Unable to write message:" + Arrays.toString(message) + " Bytes Written:" + bytesWritten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the AMBE request message
|
||||||
|
*
|
||||||
|
* @param request message
|
||||||
|
* @throws IOException if there is an error
|
||||||
|
*/
|
||||||
|
public void send(AmbeRequest request) throws IOException
|
||||||
|
{
|
||||||
|
send(request.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void receive(byte[] bytes)
|
||||||
|
{
|
||||||
|
AmbeMessage message = AmbeMessageFactory.getMessage(bytes);
|
||||||
|
|
||||||
|
if(message instanceof DecodeSpeechResponse && mAudioBufferListener != null)
|
||||||
|
{
|
||||||
|
float[] samples = ((DecodeSpeechResponse)message).getSamples();
|
||||||
|
mAudioBufferListener.receive(samples);
|
||||||
|
}
|
||||||
|
else if(message instanceof ReadyResponse && mAudioDecodeProcessorHandle == null)
|
||||||
|
{
|
||||||
|
if(mAudioDecodeProcessorHandle == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch(mAudioProtocol)
|
||||||
|
{
|
||||||
|
case DSTAR:
|
||||||
|
send(new SetVocoderRequest(VocoderRate.RATE_33));
|
||||||
|
send(new SetVocoderParametersRequest(0x0130, 0x0763, 0x4000, 0x0000, 0x0000, 0x0048));
|
||||||
|
break;
|
||||||
|
case DMR:
|
||||||
|
case NXDN:
|
||||||
|
case P25_PHASE2:
|
||||||
|
send(new SetVocoderRequest(VocoderRate.RATE_33));
|
||||||
|
send(new SetVocoderParametersRequest(0x0431, 0x0754, 0x2400, 0x0000, 0x0000, 0x6F48));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unrecognized audio protocol:" + mAudioProtocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error setting audio protocol vocoder parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("ThumbDv Reset Complete");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("ThumbDv Reset Detected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(message instanceof SetVocoderParameterResponse)
|
||||||
|
{
|
||||||
|
if(((SetVocoderParameterResponse)message).isSuccessful())
|
||||||
|
{
|
||||||
|
mLog.info("Audio vocoder parameters configured for " + mAudioProtocol);
|
||||||
|
//Start the audio frame decode processor
|
||||||
|
mAudioDecodeProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioDecodeProcessor(), 0,
|
||||||
|
10, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(message != null)
|
||||||
|
{
|
||||||
|
mLog.debug("RECEIVED:" + message.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the current settings of the serial port. Note: you must invoke start() before this method so that
|
||||||
|
* the serial port can be discovered and opened.
|
||||||
|
*/
|
||||||
|
public void logSerialPort()
|
||||||
|
{
|
||||||
|
if(mSerialPort != null)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("\nPort:\t\t\t").append(mSerialPort.getSystemPortName()).append("\n");
|
||||||
|
sb.append("Name:\t\t\t").append(mSerialPort.getDescriptivePortName()).append("\n");
|
||||||
|
sb.append("Baud Rate:\t\t").append(mSerialPort.getBaudRate()).append("\n");
|
||||||
|
sb.append("Data Bits:\t\t").append(mSerialPort.getNumDataBits()).append("\n");
|
||||||
|
sb.append("Parity Bits:\t").append(mSerialPort.getParity()).append("\n");
|
||||||
|
sb.append("Stop Bits:\t\t").append(mSerialPort.getNumStopBits()).append("\n");
|
||||||
|
sb.append("Flow Control:\t").append(mSerialPort.getFlowControlSettings()).append("\n");
|
||||||
|
sb.append("Is Open:\t\t").append(mSerialPort.isOpen()).append("\n");
|
||||||
|
|
||||||
|
mLog.info(sb.toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("No serial port found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the audio frame decode queue
|
||||||
|
*/
|
||||||
|
public class AudioDecodeProcessor implements Runnable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DecodeSpeechRequest request = mDecodeSpeechRequests.poll();
|
||||||
|
|
||||||
|
if(request != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
send(request);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error decoding audio frame", ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// //Force a 20ms sleep after submitting an audio frame to avoid overflowing the thumbdv
|
||||||
|
// Thread.sleep(20);
|
||||||
|
// }
|
||||||
|
// catch(InterruptedException ie)
|
||||||
|
// {
|
||||||
|
// //Do nothing
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
mLog.error("Error", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SerialPortReader implements Runnable
|
||||||
|
{
|
||||||
|
private InputStream mInputStream;
|
||||||
|
|
||||||
|
public SerialPortReader(InputStream inputStream)
|
||||||
|
{
|
||||||
|
mInputStream = inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(mInputStream != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while(mInputStream.available() > 0)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[400];
|
||||||
|
|
||||||
|
int bytesRead = mInputStream.readNBytes(buffer, 0, 1);
|
||||||
|
|
||||||
|
if(bytesRead == 1 && buffer[0] == PACKET_START)
|
||||||
|
{
|
||||||
|
while(mInputStream.available() < 2)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
catch(InterruptedException ie)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRead = mInputStream.readNBytes(buffer, 1, 2);
|
||||||
|
|
||||||
|
if(bytesRead == 2)
|
||||||
|
{
|
||||||
|
int length = (0xFF & buffer[1]) << 8;
|
||||||
|
length += (0xFF & buffer[2]);
|
||||||
|
|
||||||
|
if(0 < length && length < 400)
|
||||||
|
{
|
||||||
|
length++; //Add a byte for the type byte
|
||||||
|
|
||||||
|
while(mInputStream.available() < length)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
catch(InterruptedException ie)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRead = mInputStream.readNBytes(buffer, 3, length);
|
||||||
|
|
||||||
|
if(bytesRead == length)
|
||||||
|
{
|
||||||
|
receive(Arrays.copyOf(buffer, length + 3));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.debug("Expected [" + length + "] but received [" + bytesRead + "] bytes - " + Arrays.toString(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Received packet with unexpected length: " + length);
|
||||||
|
//Don't process the buffer ... let the reader read and inspect 1 byte at a time
|
||||||
|
//to regain sync on the packet start byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.debug("Expected [2] but received [" + bytesRead + "] -- this shouldn't happen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.debug("Unrecognized byte: " + Arrays.toString(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error while reading ThumbDv serial port", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
mLog.error("Error", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
mLog.debug("Starting");
|
||||||
|
|
||||||
|
String silence = "BEDDEA821EFD660C08";
|
||||||
|
|
||||||
|
String[] frames = {"0E46122323067C60F8", "0E469433C1067CF1BC", "0E46122B23067C60F8", "0E67162BE08874E2B4",
|
||||||
|
"0E46163BE1067CF1BC", "0E46122B23067C60F8", "0A06163BE00A5C303E", "0E46122B23067C60F8", "0E46163BE1847CE1FC",
|
||||||
|
"0E46122B23067C60F8"};
|
||||||
|
|
||||||
|
List<byte[]> frameData = new ArrayList<>();
|
||||||
|
|
||||||
|
for(String frame : frames)
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[frame.length() / 2];
|
||||||
|
for(int x = 0; x < frame.length(); x += 2)
|
||||||
|
{
|
||||||
|
String hex = frame.substring(x, x + 2);
|
||||||
|
bytes[x / 2] = (byte) (0xFF & Integer.parseInt(hex, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
frameData.add(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Starting thumb dv thread(s)");
|
||||||
|
|
||||||
|
final Listener<float[]> listener = packet -> {
|
||||||
|
mLog.info("Got an audio packet!");
|
||||||
|
};
|
||||||
|
|
||||||
|
try(ThumbDv thumbDv = new ThumbDv(AudioProtocol.P25_PHASE2, listener))
|
||||||
|
{
|
||||||
|
thumbDv.start();
|
||||||
|
|
||||||
|
Thread.sleep(6000);
|
||||||
|
|
||||||
|
for(int x = 0; x < 20; x++)
|
||||||
|
{
|
||||||
|
thumbDv.send(new EncodeSpeechRequest(new short[160]));
|
||||||
|
Thread.sleep(20);
|
||||||
|
}
|
||||||
|
// for(byte[] frame : frameData)
|
||||||
|
// {
|
||||||
|
// thumbDv.decode(frame);
|
||||||
|
// }
|
||||||
|
|
||||||
|
while(true);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error", ioe);
|
||||||
|
}
|
||||||
|
catch(InterruptedException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMBE-3000R Message
|
||||||
|
*/
|
||||||
|
public abstract class AmbeMessage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Converts the byte array to a hexadecimal string array
|
||||||
|
*/
|
||||||
|
public static String toHex(byte[] data)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for(byte b: data)
|
||||||
|
{
|
||||||
|
sb.append(String.format("%02X", b));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.EncodeSpeechResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.GetConfigResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.InitializeCodecResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.ProductIdResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetChannelFormatResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetChannelResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetPacketModeResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetSpeechFormatResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.UnknownResponse;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.response.VersionResponse;
|
||||||
|
|
||||||
|
public class AmbeMessageFactory
|
||||||
|
{
|
||||||
|
private static final byte CONTROL_PACKET = (byte)0x00;
|
||||||
|
private static final byte CHANNEL_PACKET = (byte)0x01;
|
||||||
|
private static final byte SPEECH_PACKET = (byte)0x02;
|
||||||
|
private static final int INDEX_PACKET_TYPE = 3;
|
||||||
|
private static final int INDEX_CONTROL_PACKET_TYPE = 4;
|
||||||
|
|
||||||
|
|
||||||
|
public static AmbeMessage getMessage(byte[] data)
|
||||||
|
{
|
||||||
|
if(data != null && data.length >= 5)
|
||||||
|
{
|
||||||
|
if(data[INDEX_PACKET_TYPE] == CONTROL_PACKET)
|
||||||
|
{
|
||||||
|
PacketField packetField = PacketField.fromValue(data[INDEX_CONTROL_PACKET_TYPE]);
|
||||||
|
|
||||||
|
switch(packetField)
|
||||||
|
{
|
||||||
|
case PKT_CHANNEL_0:
|
||||||
|
return new SetChannelResponse(data);
|
||||||
|
case PKT_CHANNEL_FORMAT:
|
||||||
|
return new SetChannelFormatResponse(data);
|
||||||
|
case PKT_CODEC_STOP:
|
||||||
|
return new SetPacketModeResponse(data);
|
||||||
|
case PKT_GET_CONFIG:
|
||||||
|
return new GetConfigResponse(data);
|
||||||
|
case PKT_INIT:
|
||||||
|
return new InitializeCodecResponse(data);
|
||||||
|
case PKT_PRODUCT_ID:
|
||||||
|
return new ProductIdResponse(data);
|
||||||
|
case PKT_RATE_PARAMETER:
|
||||||
|
return new SetVocoderParameterResponse(data);
|
||||||
|
case PKT_RATE_TABLE:
|
||||||
|
return new SetVocoderResponse(data);
|
||||||
|
case PKT_READY:
|
||||||
|
return new ReadyResponse(data);
|
||||||
|
case PKT_SPEECH_FORMAT:
|
||||||
|
return new SetSpeechFormatResponse(data);
|
||||||
|
case PKT_VERSION_STRING:
|
||||||
|
return new VersionResponse(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(data[INDEX_PACKET_TYPE] == CHANNEL_PACKET)
|
||||||
|
{
|
||||||
|
return new EncodeSpeechResponse(data);
|
||||||
|
}
|
||||||
|
else if(data[INDEX_PACKET_TYPE] == SPEECH_PACKET)
|
||||||
|
{
|
||||||
|
return new DecodeSpeechResponse(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UnknownResponse(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMBE-3000 Initialization Options
|
||||||
|
*/
|
||||||
|
public enum InitializeOption
|
||||||
|
{
|
||||||
|
ENCODER((byte)0x01),
|
||||||
|
DECODER((byte)0x02),
|
||||||
|
ENCODER_AND_DECODER((byte)0x03),
|
||||||
|
ECHO_CANCELLER((byte)0x04),
|
||||||
|
ENCODER_DECODER_ECHO_CANCELLER((byte)0x07);
|
||||||
|
|
||||||
|
private byte mCode;
|
||||||
|
|
||||||
|
InitializeOption(byte code)
|
||||||
|
{
|
||||||
|
mCode = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getCode()
|
||||||
|
{
|
||||||
|
return mCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.audio.convert.thumbdv.message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMBE-3000R Control Packet Field enumeration
|
||||||
|
*/
|
||||||
|
public enum PacketField
|
||||||
|
{
|
||||||
|
PKT_ENCODER_CMODE((byte)0x05),
|
||||||
|
PKT_DECODER_CMODE((byte)0x06),
|
||||||
|
PKT_RATE_TABLE((byte)0x09),
|
||||||
|
PKT_RATE_PARAMETER((byte)0x0A),
|
||||||
|
PKT_INIT((byte)0x0B),
|
||||||
|
PKT_LOW_POWER((byte)0x10),
|
||||||
|
PKT_CHANNEL_FORMAT((byte)0x15),
|
||||||
|
PKT_SPEECH_FORMAT((byte)0x16),
|
||||||
|
PKT_CODEC_START((byte)0x2A),
|
||||||
|
PKT_CODEC_STOP((byte)0x2B),
|
||||||
|
PKT_PRODUCT_ID((byte)0x30),
|
||||||
|
PKT_VERSION_STRING((byte)0x31),
|
||||||
|
PKT_COMPAND((byte)0x32),
|
||||||
|
PKT_RESET((byte)0x33),
|
||||||
|
PKT_RESET_TO_SOFTWARE_CONFIG((byte)0x34),
|
||||||
|
PKT_HALT((byte)0x35),
|
||||||
|
PKT_GET_CONFIG((byte)0x36),
|
||||||
|
PKT_READ_CONFIG((byte)0x37),
|
||||||
|
PKT_CODEC_CFG((byte)0x38),
|
||||||
|
PKT_READY((byte)0x39),
|
||||||
|
PKT_PARITY_MODE((byte)0x3F),
|
||||||
|
PKT_CHANNEL_0((byte)0x40),
|
||||||
|
PKT_WRITE_I2C((byte)0x44),
|
||||||
|
PKT_CLEAR_CODEC_RESET((byte)0x46),
|
||||||
|
PKT_SET_CODEC_RESET((byte)0x47),
|
||||||
|
PKT_DISCARD_CODEC_SAMPLES((byte)0x48),
|
||||||
|
PKT_DELAY_NUMBER_MICRO_SECONDS((byte)0x49),
|
||||||
|
PKT_DELAY_NUMBER_NANO_SECONDS((byte)0x4A),
|
||||||
|
PKT_GAIN((byte)0x4B),
|
||||||
|
PKT_RTS_THRESHOLD((byte)0x4E),
|
||||||
|
|
||||||
|
//Speech packet values
|
||||||
|
SPEECH_DATA((byte)0x00),
|
||||||
|
CHANNEL_DATA_HARD_SYMBOL((byte)0x01),
|
||||||
|
CMODE((byte)0x02),
|
||||||
|
SAMPLE_COUNT((byte)0x03),
|
||||||
|
TONE((byte)0x08),
|
||||||
|
CHANNEL_DATA_SOFT_SYMBOL((byte)0x17),
|
||||||
|
VOCODER((byte)0x40),
|
||||||
|
|
||||||
|
//Packet Types
|
||||||
|
PACKET_TYPE_CONTROL((byte)0x00),
|
||||||
|
PACKET_TYPE_ENCODE_SPEECH((byte)0x01),
|
||||||
|
PACKET_TYPE_DECODE_SPEECH((byte)0x02),
|
||||||
|
|
||||||
|
UNKNOWN((byte)0x00);
|
||||||
|
|
||||||
|
private byte mCode;
|
||||||
|
|
||||||
|
PacketField(byte code)
|
||||||
|
{
|
||||||
|
mCode = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packet type byte code value
|
||||||
|
*/
|
||||||
|
public byte getCode()
|
||||||
|
{
|
||||||
|
return mCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup a packet type from the code byte value
|
||||||
|
*/
|
||||||
|
public static PacketField fromValue(byte value)
|
||||||
|
{
|
||||||
|
switch(value)
|
||||||
|
{
|
||||||
|
case (byte)0x05:
|
||||||
|
return PKT_ENCODER_CMODE;
|
||||||
|
case (byte)0x06:
|
||||||
|
return PKT_DECODER_CMODE;
|
||||||
|
case (byte)0x09:
|
||||||
|
return PKT_RATE_TABLE;
|
||||||
|
case (byte)0x0A:
|
||||||
|
return PKT_RATE_PARAMETER;
|
||||||
|
case (byte)0x0B:
|
||||||
|
return PKT_INIT;
|
||||||
|
case (byte)0x10:
|
||||||
|
return PKT_LOW_POWER;
|
||||||
|
case (byte)0x15:
|
||||||
|
return PKT_CHANNEL_FORMAT;
|
||||||
|
case (byte)0x16:
|
||||||
|
return PKT_SPEECH_FORMAT;
|
||||||
|
case (byte)0x2A:
|
||||||
|
return PKT_CODEC_START;
|
||||||
|
case (byte)0x2B:
|
||||||
|
return PKT_CODEC_STOP;
|
||||||
|
case (byte)0x30:
|
||||||
|
return PKT_PRODUCT_ID;
|
||||||
|
case (byte)0x31:
|
||||||
|
return PKT_VERSION_STRING;
|
||||||
|
case (byte)0x32:
|
||||||
|
return PKT_COMPAND;
|
||||||
|
case (byte)0x33:
|
||||||
|
return PKT_RESET;
|
||||||
|
case (byte)0x34:
|
||||||
|
return PKT_RESET_TO_SOFTWARE_CONFIG;
|
||||||
|
case (byte)0x35:
|
||||||
|
return PKT_HALT;
|
||||||
|
case (byte)0x36:
|
||||||
|
return PKT_GET_CONFIG;
|
||||||
|
case (byte)0x37:
|
||||||
|
return PKT_READ_CONFIG;
|
||||||
|
case (byte)0x38:
|
||||||
|
return PKT_CODEC_CFG;
|
||||||
|
case (byte)0x39:
|
||||||
|
return PKT_READY;
|
||||||
|
case (byte)0x3F:
|
||||||
|
return PKT_PARITY_MODE;
|
||||||
|
case (byte)0x40:
|
||||||
|
return PKT_CHANNEL_0;
|
||||||
|
case (byte)0x44:
|
||||||
|
return PKT_WRITE_I2C;
|
||||||
|
case (byte)0x46:
|
||||||
|
return PKT_CLEAR_CODEC_RESET;
|
||||||
|
case (byte)0x47:
|
||||||
|
return PKT_SET_CODEC_RESET;
|
||||||
|
case (byte)0x48:
|
||||||
|
return PKT_DISCARD_CODEC_SAMPLES;
|
||||||
|
case (byte)0x49:
|
||||||
|
return PKT_DELAY_NUMBER_MICRO_SECONDS;
|
||||||
|
case (byte)0x4A:
|
||||||
|
return PKT_DELAY_NUMBER_NANO_SECONDS;
|
||||||
|
case (byte)0x4B:
|
||||||
|
return PKT_GAIN;
|
||||||
|
case (byte)0x4E:
|
||||||
|
return PKT_RTS_THRESHOLD;
|
||||||
|
default:
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message;
|
||||||
|
|
||||||
|
public enum VocoderRate
|
||||||
|
{
|
||||||
|
//AMBE-1000 Standard Rates
|
||||||
|
RATE_0((byte)0x00, 2400,0),
|
||||||
|
RATE_1((byte)0x01, 3600,0),
|
||||||
|
RATE_2((byte)0x02, 3600,1200),
|
||||||
|
RATE_3((byte)0x03, 4800,0),
|
||||||
|
RATE_4((byte)0x04, 9600,0),
|
||||||
|
RATE_5((byte)0x05, 2350,50),
|
||||||
|
RATE_6((byte)0x06, 4850,4750),
|
||||||
|
RATE_7((byte)0x07, 4550,250),
|
||||||
|
RATE_8((byte)0x08, 3100,1700),
|
||||||
|
RATE_9((byte)0x09, 4400,2800),
|
||||||
|
RATE_10((byte)0x0A, 4150,2250),
|
||||||
|
RATE_11((byte)0x0B, 3350,250),
|
||||||
|
RATE_12((byte)0x0C, 7750,250),
|
||||||
|
RATE_13((byte)0x0D, 4650,3350),
|
||||||
|
RATE_14((byte)0x0E, 3750,250),
|
||||||
|
RATE_15((byte)0x0F, 4000,0),
|
||||||
|
|
||||||
|
//AMBE-2000 Standard Rates
|
||||||
|
RATE_16((byte)0x10, 3600,0),
|
||||||
|
RATE_17((byte)0x11, 4000,0),
|
||||||
|
RATE_18((byte)0x12, 4800,0),
|
||||||
|
RATE_19((byte)0x13, 6400,0),
|
||||||
|
RATE_20((byte)0x14, 8000,0),
|
||||||
|
RATE_21((byte)0x15, 9600,0),
|
||||||
|
RATE_22((byte)0x16, 2400,1600),
|
||||||
|
RATE_23((byte)0x17, 3600,1200),
|
||||||
|
RATE_24((byte)0x18, 4000,800),
|
||||||
|
RATE_25((byte)0x19, 2400,2400),
|
||||||
|
RATE_26((byte)0x1A, 4000,2400),
|
||||||
|
RATE_27((byte)0x1B, 4400,2800), //Is this compatible with APCO25 Phase 1?
|
||||||
|
RATE_28((byte)0x1C, 4000,4000),
|
||||||
|
RATE_29((byte)0x1D, 2400,7200),
|
||||||
|
RATE_30((byte)0x1E, 3600,6000),
|
||||||
|
RATE_31((byte)0x1F, 2000,0),
|
||||||
|
RATE_32((byte)0x20, 3600,2800),
|
||||||
|
|
||||||
|
//AMBE-3000 Standard Rates (AMBE+2)
|
||||||
|
RATE_33((byte)0x21, 2450,1150), //APCO25 HR (Phase 2), DMR, NXDN, DSTAR
|
||||||
|
RATE_34((byte)0x22, 2450,0),
|
||||||
|
RATE_35((byte)0x23, 2250,1150),
|
||||||
|
RATE_36((byte)0x24, 2250,0),
|
||||||
|
RATE_37((byte)0x25, 2400,0),
|
||||||
|
RATE_38((byte)0x26, 3000,0),
|
||||||
|
RATE_39((byte)0x27, 3600,0),
|
||||||
|
RATE_40((byte)0x28, 4000,0),
|
||||||
|
RATE_41((byte)0x29, 4400,0),
|
||||||
|
RATE_42((byte)0x2A, 4800,0),
|
||||||
|
RATE_43((byte)0x2B, 6400,0),
|
||||||
|
RATE_44((byte)0x2C, 7200,0),
|
||||||
|
RATE_45((byte)0x2D, 8000,0),
|
||||||
|
RATE_46((byte)0x2E, 9600,0),
|
||||||
|
RATE_47((byte)0x2F, 2450,250),
|
||||||
|
RATE_48((byte)0x30, 3350,250),
|
||||||
|
RATE_49((byte)0x31, 3750,250),
|
||||||
|
RATE_50((byte)0x32, 4550,250),
|
||||||
|
RATE_51((byte)0x33, 2450,1950),
|
||||||
|
RATE_52((byte)0x34, 2450,2350),
|
||||||
|
RATE_53((byte)0x35, 2450,3550),
|
||||||
|
RATE_54((byte)0x36, 2450,4750),
|
||||||
|
RATE_55((byte)0x37, 2600,1400),
|
||||||
|
RATE_56((byte)0x38, 3600,1200),
|
||||||
|
RATE_57((byte)0x39, 4000,800),
|
||||||
|
RATE_58((byte)0x3A, 4000,2400),
|
||||||
|
RATE_59((byte)0x3B, 4400,2800), //Is this compatible with APCO25 Phase 1?
|
||||||
|
RATE_60((byte)0x3C, 4000,4000),
|
||||||
|
RATE_61((byte)0x3D, 3600,6000);
|
||||||
|
|
||||||
|
private byte mCode;
|
||||||
|
private int mSpeechRate;
|
||||||
|
private int mFecRate;
|
||||||
|
|
||||||
|
VocoderRate(byte code, int speechRate, int fecRate)
|
||||||
|
{
|
||||||
|
mCode = code;
|
||||||
|
mSpeechRate = speechRate;
|
||||||
|
mFecRate = fecRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Byte code value for the vocoder rate
|
||||||
|
*/
|
||||||
|
public byte getCode()
|
||||||
|
{
|
||||||
|
return mCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speech rate in bits per second (bps)
|
||||||
|
*/
|
||||||
|
public int getSpeechRate()
|
||||||
|
{
|
||||||
|
return mSpeechRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward Error Correction rate in bits per second (bps)
|
||||||
|
*/
|
||||||
|
public int getFecRate()
|
||||||
|
{
|
||||||
|
return mFecRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMBE-3000R Request Packet
|
||||||
|
*/
|
||||||
|
public abstract class AmbeRequest extends AmbeMessage
|
||||||
|
{
|
||||||
|
private static final byte PACKET_START_BYTE = (byte)0x61;
|
||||||
|
protected static final int PACKET_TYPE_INDEX = 3;
|
||||||
|
protected static final int PAYLOAD_START_INDEX = 4;
|
||||||
|
|
||||||
|
public abstract PacketField getType();
|
||||||
|
public abstract byte[] getData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a byte array of the specified length plus 4 packet header bytes with packet start, length and control
|
||||||
|
* bytes filled in.
|
||||||
|
*
|
||||||
|
* @param length of the message (not including the 4 byte packet header)
|
||||||
|
* @return byte array with packet header pre-filled.
|
||||||
|
*/
|
||||||
|
protected byte[] createMessage(int length, PacketField type)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[length + 4];
|
||||||
|
|
||||||
|
data[0] = PACKET_START_BYTE;
|
||||||
|
data[1] = (byte)((length >> 8 & 0xFF));
|
||||||
|
data[2] = (byte)(length & 0xFF);
|
||||||
|
|
||||||
|
if(type == PacketField.PACKET_TYPE_ENCODE_SPEECH || type == PacketField.PACKET_TYPE_DECODE_SPEECH)
|
||||||
|
{
|
||||||
|
data[PACKET_TYPE_INDEX] = type.getCode();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data[PACKET_TYPE_INDEX] = PacketField.PACKET_TYPE_CONTROL.getCode();
|
||||||
|
data[PAYLOAD_START_INDEX] = type.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode speech request, used to request decode of an encoded audio frame.
|
||||||
|
*/
|
||||||
|
public class DecodeSpeechRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
private static final int CHANNEL_DATA_IDENTIFIER_INDEX = 4;
|
||||||
|
private static final byte SAMPLE_COUNT = (byte)(0xFF & 160); //8 kHz Audio sample count for 20ms frame
|
||||||
|
|
||||||
|
private byte[] mAudioFrame;
|
||||||
|
private VocoderRate mVocoderRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an audio frame decode request using the specified vocoder rate.
|
||||||
|
* @param audioFrame of encoded audio samples
|
||||||
|
* @param vocoderRate to use when decoding
|
||||||
|
*/
|
||||||
|
public DecodeSpeechRequest(byte[] audioFrame, VocoderRate vocoderRate)
|
||||||
|
{
|
||||||
|
mAudioFrame = audioFrame;
|
||||||
|
mVocoderRate = vocoderRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an audio frame decode request using the current vocoder rate.
|
||||||
|
* @param audioFrame of encoded audio samples
|
||||||
|
*/
|
||||||
|
public DecodeSpeechRequest(byte[] audioFrame)
|
||||||
|
{
|
||||||
|
this(audioFrame, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PACKET_TYPE_ENCODE_SPEECH;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasVocoderRate()
|
||||||
|
{
|
||||||
|
return mVocoderRate != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
if(hasVocoderRate())
|
||||||
|
{
|
||||||
|
int length = mAudioFrame.length + 9;
|
||||||
|
|
||||||
|
byte[] data = createMessage(length, getType());
|
||||||
|
int offset = CHANNEL_DATA_IDENTIFIER_INDEX;
|
||||||
|
|
||||||
|
data[offset++] = PacketField.VOCODER.getCode();
|
||||||
|
data[offset++] = mVocoderRate.getCode();
|
||||||
|
|
||||||
|
data[offset++] = PacketField.CHANNEL_DATA_HARD_SYMBOL.getCode();
|
||||||
|
data[offset++] = (byte)(0xFF & (mAudioFrame.length * 8));
|
||||||
|
System.arraycopy(mAudioFrame, 0, data, offset, mAudioFrame.length);
|
||||||
|
offset += mAudioFrame.length;
|
||||||
|
data[offset++] = PacketField.SAMPLE_COUNT.getCode();
|
||||||
|
data[offset++] = (byte)0xA0;
|
||||||
|
data[offset++] = (byte)0x02;
|
||||||
|
data[offset++] = (byte)0x00;
|
||||||
|
data[offset] = (byte)0x00;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int length = mAudioFrame.length + 2;
|
||||||
|
byte[] data = createMessage(length, getType());
|
||||||
|
|
||||||
|
int offset = CHANNEL_DATA_IDENTIFIER_INDEX;
|
||||||
|
data[offset++] = PacketField.CHANNEL_DATA_HARD_SYMBOL.getCode();
|
||||||
|
data[offset++] = (byte)(0xFF & (mAudioFrame.length * 8));
|
||||||
|
System.arraycopy(mAudioFrame, 0, data, offset, mAudioFrame.length);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
public class EncodeSpeechRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
private static final int CHANNEL_IDENTIFIER_INDEX = 4;
|
||||||
|
private static final int SPEECH_DATA_IDENTIFIER_INDEX = 5;
|
||||||
|
private static final int SAMPLE_COUNT_INDEX = 6;
|
||||||
|
private static final int SPEECH_DATA_START_INDEX = 7;
|
||||||
|
|
||||||
|
private short[] mSamples;
|
||||||
|
|
||||||
|
public EncodeSpeechRequest(short[] samples)
|
||||||
|
{
|
||||||
|
mSamples = samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncodeSpeechRequest(float[] samples)
|
||||||
|
{
|
||||||
|
mSamples = new short[samples.length];
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length; x++)
|
||||||
|
{
|
||||||
|
if(samples[x] > 1.0f)
|
||||||
|
{
|
||||||
|
mSamples[x] = Short.MAX_VALUE;
|
||||||
|
}
|
||||||
|
else if(samples[x] < -1.0f)
|
||||||
|
{
|
||||||
|
mSamples[x] = Short.MIN_VALUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSamples[x] = (short)(samples[x] * Short.MAX_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PACKET_TYPE_DECODE_SPEECH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
int length = (mSamples.length * 2) + 3;
|
||||||
|
byte[] data = createMessage(length, getType());
|
||||||
|
|
||||||
|
data[CHANNEL_IDENTIFIER_INDEX] = PacketField.PKT_CHANNEL_0.getCode();
|
||||||
|
data[SPEECH_DATA_IDENTIFIER_INDEX] = (byte)0x00;
|
||||||
|
data[SAMPLE_COUNT_INDEX] = (byte)(0xFF & mSamples.length);
|
||||||
|
|
||||||
|
int pointer = SPEECH_DATA_START_INDEX;
|
||||||
|
|
||||||
|
for(short sample: mSamples)
|
||||||
|
{
|
||||||
|
data[pointer++] = (byte)((sample >> 8 & 0xFF));
|
||||||
|
data[pointer++] = (byte)(sample & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration request packet
|
||||||
|
*/
|
||||||
|
public class GetConfigRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_GET_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
return createMessage(1, getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.InitializeOption;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize CODEC request packet
|
||||||
|
*/
|
||||||
|
public class InitializeCodecRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
private static final int INITIALIZE_OPTION_INDEX = 5;
|
||||||
|
private InitializeOption mInitializeOption;
|
||||||
|
|
||||||
|
public InitializeCodecRequest(InitializeOption option)
|
||||||
|
{
|
||||||
|
mInitializeOption = option;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
byte[] data = createMessage(2, getType());
|
||||||
|
data[INITIALIZE_OPTION_INDEX] = mInitializeOption.getCode();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product ID request packet
|
||||||
|
*/
|
||||||
|
public class ProductIdRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
return createMessage(1, getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset request packet
|
||||||
|
*/
|
||||||
|
public class ResetRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_RESET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
return createMessage(1, getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set channel format request packet
|
||||||
|
*/
|
||||||
|
public class SetChannelFormatRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
private static final int FORMAT_INDEX = 5;
|
||||||
|
private static final byte FORMAT = (byte)0x00;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_CHANNEL_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
byte[] data = createMessage(2, getType());
|
||||||
|
data[FORMAT_INDEX] = FORMAT;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set channel request packet
|
||||||
|
*/
|
||||||
|
public class SetChannelRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_CHANNEL_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
return createMessage(1, getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packet mode (versus CODEC mode) request packet
|
||||||
|
*/
|
||||||
|
public class SetPacketModeRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_CODEC_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
return createMessage(1, getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set speech format request packet
|
||||||
|
*/
|
||||||
|
public class SetSpeechFormatRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
private static final int FORMAT_INDEX = 5;
|
||||||
|
private static final byte FORMAT = (byte)0x00;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_SPEECH_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
byte[] data = createMessage(2, getType());
|
||||||
|
data[FORMAT_INDEX] = FORMAT;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Vocoder parameters request packet
|
||||||
|
*/
|
||||||
|
public class SetVocoderParametersRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
private int mWord0;
|
||||||
|
private int mWord1;
|
||||||
|
private int mWord2;
|
||||||
|
private int mWord3;
|
||||||
|
private int mWord4;
|
||||||
|
private int mWord5;
|
||||||
|
|
||||||
|
public SetVocoderParametersRequest(int word0, int word1, int word2, int word3, int word4, int word5)
|
||||||
|
{
|
||||||
|
mWord0 = word0;
|
||||||
|
mWord1 = word1;
|
||||||
|
mWord2 = word2;
|
||||||
|
mWord3 = word3;
|
||||||
|
mWord4 = word4;
|
||||||
|
mWord5 = word5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_RATE_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
byte[] data = createMessage(14, getType());
|
||||||
|
data[PAYLOAD_START_INDEX + 1] = (byte)(mWord0 >> 8 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 2] = (byte)(mWord0 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 3] = (byte)(mWord1 >> 8 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 4] = (byte)(mWord1 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 5] = (byte)(mWord2 >> 8 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 6] = (byte)(mWord2 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 7] = (byte)(mWord3 >> 8 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 8] = (byte)(mWord3 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 9] = (byte)(mWord4 >> 8 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 10] = (byte)(mWord4 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 11] = (byte)(mWord5 >> 8 & 0xFF);
|
||||||
|
data[PAYLOAD_START_INDEX + 12] = (byte)(mWord5 & 0xFF);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Vocoder Rate request packet
|
||||||
|
*/
|
||||||
|
public class SetVocoderRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
private VocoderRate mVocoderRate;
|
||||||
|
|
||||||
|
public SetVocoderRequest(VocoderRate rate)
|
||||||
|
{
|
||||||
|
mVocoderRate = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_RATE_TABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
byte[] data = createMessage(2, getType());
|
||||||
|
data[PAYLOAD_START_INDEX + 1] = mVocoderRate.getCode();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version string request packet
|
||||||
|
*/
|
||||||
|
public class VersionRequest extends AmbeRequest
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_VERSION_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getData()
|
||||||
|
{
|
||||||
|
return createMessage(1, getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMBE-3000R Response Packet
|
||||||
|
*/
|
||||||
|
public abstract class AmbeResponse extends AmbeMessage
|
||||||
|
{
|
||||||
|
protected static final int PAYLOAD_START_INDEX = 5;
|
||||||
|
private byte[] mMessage;
|
||||||
|
|
||||||
|
protected AmbeResponse(byte[] message)
|
||||||
|
{
|
||||||
|
mMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control packet type
|
||||||
|
*/
|
||||||
|
public abstract PacketField getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Received message bytes
|
||||||
|
*/
|
||||||
|
protected byte[] getMessage()
|
||||||
|
{
|
||||||
|
return mMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload of the packet (does not include the packet header)
|
||||||
|
*/
|
||||||
|
protected byte[] getPayload()
|
||||||
|
{
|
||||||
|
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX, getMessage().length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* sdr-trunk
|
||||||
|
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||||
|
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
* later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with this program.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
import io.github.dsheirer.sample.ConversionUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode speech response
|
||||||
|
*/
|
||||||
|
public class DecodeSpeechResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public DecodeSpeechResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PACKET_TYPE_DECODE_SPEECH;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload of the packet (does not include the packet header)
|
||||||
|
*/
|
||||||
|
protected byte[] getPayload()
|
||||||
|
{
|
||||||
|
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX + 1, getMessage().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getSamples()
|
||||||
|
{
|
||||||
|
return ConversionUtils.convertFromSigned16BitSamples(getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "DECODED SPEECH: SAMPLE COUNT:" + getSamples().length + " " + toHex(getPayload());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode speech response
|
||||||
|
*/
|
||||||
|
public class EncodeSpeechResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public EncodeSpeechResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PACKET_TYPE_ENCODE_SPEECH;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload of the packet (does not include the packet header)
|
||||||
|
*/
|
||||||
|
protected byte[] getPayload()
|
||||||
|
{
|
||||||
|
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX + 1, getMessage().length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "ENCODED SPEECH: " + toHex(getPayload()) + " " + Arrays.toString(getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration response
|
||||||
|
*/
|
||||||
|
public class GetConfigResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public GetConfigResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_GET_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "CONFIGURATION: " + toHex(getPayload());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize CODEC response
|
||||||
|
*/
|
||||||
|
public class InitializeCodecResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public InitializeCodecResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success / fail
|
||||||
|
*/
|
||||||
|
public boolean isSuccessful()
|
||||||
|
{
|
||||||
|
byte[] payload = getPayload();
|
||||||
|
|
||||||
|
return payload != null && payload.length ==1 && payload[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "INITIALIZE CODEC " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product ID Response
|
||||||
|
*/
|
||||||
|
public class ProductIdResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public ProductIdResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_PRODUCT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product ID string
|
||||||
|
*/
|
||||||
|
public String getProductId()
|
||||||
|
{
|
||||||
|
return new String(getPayload()).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "PRODUCT ID:" + getProductId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ready response
|
||||||
|
*/
|
||||||
|
public class ReadyResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public ReadyResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "READY" + Arrays.toString(getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset response
|
||||||
|
*/
|
||||||
|
public class ResetResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public ResetResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_RESET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success indicator
|
||||||
|
*/
|
||||||
|
public boolean isSuccessful()
|
||||||
|
{
|
||||||
|
byte[] payload = getPayload();
|
||||||
|
return payload != null && payload.length ==1 && payload[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "RESET " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set channel format response
|
||||||
|
*/
|
||||||
|
public class SetChannelFormatResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public SetChannelFormatResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_CHANNEL_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success or fail
|
||||||
|
*/
|
||||||
|
public boolean isSuccessful()
|
||||||
|
{
|
||||||
|
byte[] payload = getPayload();
|
||||||
|
|
||||||
|
return payload != null && payload.length ==1 && payload[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "SET CHANNEL FORMAT " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Vocoder rate response
|
||||||
|
*/
|
||||||
|
public class SetChannelResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public SetChannelResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_CHANNEL_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success indicator
|
||||||
|
*/
|
||||||
|
public boolean isSuccessful()
|
||||||
|
{
|
||||||
|
byte[] payload = getPayload();
|
||||||
|
return payload != null && payload.length ==1 && payload[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "SET CHANNEL 0 " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set packet mode response
|
||||||
|
*/
|
||||||
|
public class SetPacketModeResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public SetPacketModeResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_CODEC_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success or fail
|
||||||
|
*/
|
||||||
|
public boolean isSuccessful()
|
||||||
|
{
|
||||||
|
byte[] payload = getPayload();
|
||||||
|
|
||||||
|
return payload != null && payload.length ==1 && payload[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "SET PACKET MODE " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set speech format response
|
||||||
|
*/
|
||||||
|
public class SetSpeechFormatResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public SetSpeechFormatResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_SPEECH_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success or fail
|
||||||
|
*/
|
||||||
|
public boolean isSuccessful()
|
||||||
|
{
|
||||||
|
byte[] payload = getPayload();
|
||||||
|
|
||||||
|
return payload != null && payload.length ==1 && payload[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "SET SPEECH FORMAT " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Vocoder parameter response
|
||||||
|
*/
|
||||||
|
public class SetVocoderParameterResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public SetVocoderParameterResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_RATE_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success indicator
|
||||||
|
*/
|
||||||
|
public boolean isSuccessful()
|
||||||
|
{
|
||||||
|
byte[] payload = getPayload();
|
||||||
|
|
||||||
|
return payload != null && payload.length ==3 && payload[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if(isSuccessful())
|
||||||
|
{
|
||||||
|
return "SET VOCODER RATE SUCCESSFUL";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "SET VOCODER RATE **FAILED** - RESPONSE:" + toHex(getPayload());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Vocoder rate response
|
||||||
|
*/
|
||||||
|
public class SetVocoderResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public SetVocoderResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_RATE_TABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Success indicator
|
||||||
|
*/
|
||||||
|
public boolean isSuccessful()
|
||||||
|
{
|
||||||
|
byte[] payload = getPayload();
|
||||||
|
|
||||||
|
return payload != null && payload.length ==1 && payload[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "SET VOCODER RATE " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown Response Message
|
||||||
|
*/
|
||||||
|
public class UnknownResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public UnknownResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "UNKNOWN MESSAGE:" + Arrays.toString(getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
||||||
|
|
||||||
|
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version Response
|
||||||
|
*/
|
||||||
|
public class VersionResponse extends AmbeResponse
|
||||||
|
{
|
||||||
|
public VersionResponse(byte[] message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketField getType()
|
||||||
|
{
|
||||||
|
return PacketField.PKT_VERSION_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version string
|
||||||
|
*/
|
||||||
|
public String getVersion()
|
||||||
|
{
|
||||||
|
return new String(getPayload()).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "VERSION:" + getVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -257,20 +257,6 @@ public abstract class C4FMSymbolProcessor
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum bit errors allowed in a detected sync pattern while acquiring (ie not synchronized), measured from the
|
|
||||||
* symbol decisions at the optimized symbol timing. Sync detections with more bit errors are rejected. Bit errors
|
|
||||||
* include symbol magnitude (ie +3 vs +1) errors, so this rejects noise where the symbol signs correlate with the
|
|
||||||
* sync pattern but the symbol magnitudes are inconsistent. Once synchronized, the limit isn't applied since the
|
|
||||||
* sync detection timing is known and rejecting a valid sync detection costs the burst. Default implementation
|
|
||||||
* imposes no limit.
|
|
||||||
* @return maximum sync pattern bit error count while acquiring.
|
|
||||||
*/
|
|
||||||
public int getSyncAcquisitionMaxBitErrors()
|
|
||||||
{
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of consecutive sync periods (capped at 3) where a sync was detected within the max message length.
|
* Number of consecutive sync periods (capped at 3) where a sync was detected within the max message length.
|
||||||
*/
|
*/
|
||||||
@@ -959,14 +945,12 @@ public abstract class C4FMSymbolProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the optimization score is high quality and, while acquiring, the sync pattern bit errors don't
|
* Indicates if this correction has a valid NID or the optimization (or detection) score is high quality.
|
||||||
* exceed the max.
|
|
||||||
* @return true if valid.
|
* @return true if valid.
|
||||||
*/
|
*/
|
||||||
public boolean isValid()
|
public boolean isValid()
|
||||||
{
|
{
|
||||||
return mOptimizationScore > getSyncThresholdEqualized() &&
|
return mOptimizationScore > getSyncThresholdEqualized();
|
||||||
(mSynchronized || mCorrectedBitCount <= getSyncAcquisitionMaxBitErrors());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1313,7 +1297,7 @@ public abstract class C4FMSymbolProcessor
|
|||||||
|
|
||||||
for(int x = 0; x < patternSymbols.length; x++)
|
for(int x = 0; x < patternSymbols.length; x++)
|
||||||
{
|
{
|
||||||
if(bufferPointer >= 0 && bufferPointer < maxPointer)
|
if(bufferPointer < maxPointer)
|
||||||
{
|
{
|
||||||
softSymbol = interpolate(mBuffer[bufferPointer], mBuffer[bufferPointer + 1], fractional) * gain + balance;
|
softSymbol = interpolate(mBuffer[bufferPointer], mBuffer[bufferPointer + 1], fractional) * gain + balance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import javax.swing.JLabel;
|
|||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -576,17 +575,7 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
@Override
|
@Override
|
||||||
public void mouseWheelMoved(MouseWheelEvent e)
|
public void mouseWheelMoved(MouseWheelEvent e)
|
||||||
{
|
{
|
||||||
//Swing delivers mouse wheel events to disabled components, so check enabled state explicitly so that
|
set(mValue - e.getWheelRotation());
|
||||||
//the wheel can't bypass the lock when the tuner is providing channels.
|
|
||||||
if(isEnabled())
|
|
||||||
{
|
|
||||||
set(mValue - e.getWheelRotation());
|
|
||||||
}
|
|
||||||
else if(getParent() != null)
|
|
||||||
{
|
|
||||||
//Pass the event up so that an enclosing scroll pane can still scroll while this control is locked
|
|
||||||
getParent().dispatchEvent(SwingUtilities.convertMouseEvent(Digit.this, e, getParent()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
|||||||
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
import io.github.dsheirer.gui.control.AccessibleToggleSwitch;
|
import io.github.dsheirer.gui.control.AccessibleToggleSwitch;
|
||||||
import io.github.dsheirer.gui.control.IntegerTextField;
|
|
||||||
import io.github.dsheirer.gui.playlist.Editor;
|
import io.github.dsheirer.gui.playlist.Editor;
|
||||||
import io.github.dsheirer.playlist.PlaylistManager;
|
import io.github.dsheirer.playlist.PlaylistManager;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@@ -69,26 +68,6 @@ public abstract class AbstractBroadcastEditor<T extends BroadcastConfiguration>
|
|||||||
buttonBox.setSpacing(10);
|
buttonBox.setSpacing(10);
|
||||||
buttonBox.getChildren().addAll(getSaveButton(), getResetButton());
|
buttonBox.getChildren().addAll(getSaveButton(), getResetButton());
|
||||||
|
|
||||||
/**
|
|
||||||
* Allow column 1 (Primary input fields) to expand with editor width
|
|
||||||
*/
|
|
||||||
for(Node node : getEditorPane().getChildren())
|
|
||||||
{
|
|
||||||
Integer column = GridPane.getColumnIndex(node);
|
|
||||||
|
|
||||||
if(column != null && column == 1)
|
|
||||||
{
|
|
||||||
if(node instanceof IntegerTextField)
|
|
||||||
{
|
|
||||||
GridPane.setFillWidth(node, false);
|
|
||||||
}
|
|
||||||
else if(node instanceof TextField)
|
|
||||||
{
|
|
||||||
GridPane.setHgrow(node, Priority.ALWAYS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HBox editorBox = new HBox();
|
HBox editorBox = new HBox();
|
||||||
HBox.setHgrow(getEditorPane(), Priority.ALWAYS);
|
HBox.setHgrow(getEditorPane(), Priority.ALWAYS);
|
||||||
editorBox.getChildren().addAll(getEditorPane(), buttonBox);
|
editorBox.getChildren().addAll(getEditorPane(), buttonBox);
|
||||||
|
|||||||
@@ -298,10 +298,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
if(message instanceof VoiceMessage voice)
|
if(message instanceof VoiceMessage voice)
|
||||||
{
|
{
|
||||||
if(isValid(voice))
|
processVoice(voice);
|
||||||
{
|
|
||||||
processVoice(voice);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if(message instanceof DataMessage data)
|
else if(message instanceof DataMessage data)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,12 +66,6 @@ public class DMRSymbolProcessor extends C4FMSymbolProcessor
|
|||||||
private static final float SYNC_THRESHOLD_EQUALIZED = 70;
|
private static final float SYNC_THRESHOLD_EQUALIZED = 70;
|
||||||
//Once synchronized, sync detections are only allowed within this many symbols of a burst boundary.
|
//Once synchronized, sync detections are only allowed within this many symbols of a burst boundary.
|
||||||
private static final int SYNC_WINDOW_SYMBOLS = 2;
|
private static final int SYNC_WINDOW_SYMBOLS = 2;
|
||||||
//Maximum bit errors (of 48) in a detected sync pattern while acquiring. Testing: all false sync detections in
|
|
||||||
//600 seconds of noise occurred while acquiring, with 7+ (mostly 12+) bit errors, and a max of 8 eliminated them
|
|
||||||
//(245 bursts to 0) at a cost of < 0.6% valid data bursts on clean recordings and 0.3 - 5.5% on multipath
|
|
||||||
//recordings with injected noise, where valid link control improved. A max of 10 leaks noise false syncs and 6
|
|
||||||
//costs more multipath data bursts. Applying the max once synchronized cost 3 - 20% of valid bursts.
|
|
||||||
private static final int SYNC_ACQUISITION_MAX_BIT_ERRORS = 8;
|
|
||||||
private final DMRSyncModeMonitor mSyncModeMonitor = new DMRSyncModeMonitor();
|
private final DMRSyncModeMonitor mSyncModeMonitor = new DMRSyncModeMonitor();
|
||||||
private final DMRMessageFramer mMessageFramer;
|
private final DMRMessageFramer mMessageFramer;
|
||||||
|
|
||||||
@@ -148,12 +142,6 @@ public class DMRSymbolProcessor extends C4FMSymbolProcessor
|
|||||||
return Math.min(offset, BURST_DIBIT_LENGTH - offset) <= SYNC_WINDOW_SYMBOLS;
|
return Math.min(offset, BURST_DIBIT_LENGTH - offset) <= SYNC_WINDOW_SYMBOLS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSyncAcquisitionMaxBitErrors()
|
|
||||||
{
|
|
||||||
return SYNC_ACQUISITION_MAX_BIT_ERRORS;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDynamicBalanceTrackingRequired()
|
public boolean isDynamicBalanceTrackingRequired()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -105,13 +105,6 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
|
|||||||
{
|
{
|
||||||
if(hasAudioCodec() && message.getTimeslot() == getTimeslot())
|
if(hasAudioCodec() && message.getTimeslot() == getTimeslot())
|
||||||
{
|
{
|
||||||
//Ignore voice messages where none of the AMBE voice frames pass error detection, so that invalid voice
|
|
||||||
//messages (ie false sync detections) don't produce audio or establish the encryption state.
|
|
||||||
if(message instanceof VoiceMessage voiceMessage && !voiceMessage.isValid())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Attempt to set the audio encryption state from certain types of messages
|
//Attempt to set the audio encryption state from certain types of messages
|
||||||
if(!mEncryptedCallStateEstablished)
|
if(!mEncryptedCallStateEstablished)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,207 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 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.voice;
|
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error detection for the three AMBE+2 (3600 x 2450) voice frames carried in a DMR voice burst.
|
|
||||||
*
|
|
||||||
* Each 72-bit voice frame carries an extended Golay(24,12) protected C0 vector and a Golay(23,12) protected C1 vector
|
|
||||||
* that is scrambled with a pseudo-random sequence seeded by the 12 data bits of C0. The C2 and C3 vectors are not
|
|
||||||
* protected. Golay(23,12) is a perfect code where every 23-bit word decodes to a codeword within 3 bit errors, so a
|
|
||||||
* frame is only considered valid when the combined C0 and C1 error count is low, which random (ie noise) bits rarely
|
|
||||||
* achieve.
|
|
||||||
*
|
|
||||||
* Vector bit indices and the C1 scrambling sequence are from the JMBE AMBEFrame decoder. Burst bit indices of each
|
|
||||||
* voice frame match VoiceMessage.getAMBEFrames(): frames 1 and 3 are contiguous and frame 2 is split around the 48-bit
|
|
||||||
* sync/embedded signalling field in the middle of the burst.
|
|
||||||
*/
|
|
||||||
public class AMBEFrameValidator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Maximum combined C0 and C1 bit errors for a valid voice frame. Random 47-bit sequences have a combined error
|
|
||||||
* count of 2 or less with a probability of ~1.3e-4.
|
|
||||||
*/
|
|
||||||
public static final int MAX_ERRORS = 2;
|
|
||||||
|
|
||||||
private static final int[] VECTOR_C0 = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 1, 5,
|
|
||||||
9, 13, 17, 21};
|
|
||||||
private static final int[] VECTOR_C1 = {25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 2, 6, 10, 14, 18, 22, 26,
|
|
||||||
30, 34, 38, 42};
|
|
||||||
private static final int[] FRAME_OFFSETS = {24, 96, 216};
|
|
||||||
private static final int GOLAY_23_GENERATOR = 0xC75;
|
|
||||||
private static final int[][] C0_INDICES = new int[3][VECTOR_C0.length];
|
|
||||||
private static final int[][] C1_INDICES = new int[3][VECTOR_C1.length];
|
|
||||||
//Minimum weight error pattern for each Golay(23,12) syndrome (perfect code: all syndromes have weight <= 3)
|
|
||||||
private static final int[] ERROR_PATTERNS = new int[2048];
|
|
||||||
//C1 descrambling sequence for each 12-bit C0 data (u0) seed value, MSB first
|
|
||||||
private static final int[] SCRAMBLE_SEQUENCES = new int[4096];
|
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
for(int frame = 0; frame < 3; frame++)
|
|
||||||
{
|
|
||||||
for(int x = 0; x < VECTOR_C0.length; x++)
|
|
||||||
{
|
|
||||||
C0_INDICES[frame][x] = getBurstIndex(frame, VECTOR_C0[x]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int x = 0; x < VECTOR_C1.length; x++)
|
|
||||||
{
|
|
||||||
C1_INDICES[frame][x] = getBurstIndex(frame, VECTOR_C1[x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Golay(23,12) is perfect: the 2048 error patterns of weight 0 - 3 map one-to-one onto the 2048 syndromes.
|
|
||||||
for(int i = 0; i < 23; i++)
|
|
||||||
{
|
|
||||||
ERROR_PATTERNS[getSyndrome(1 << i)] = 1 << i;
|
|
||||||
|
|
||||||
for(int j = i + 1; j < 23; j++)
|
|
||||||
{
|
|
||||||
ERROR_PATTERNS[getSyndrome((1 << i) | (1 << j))] = (1 << i) | (1 << j);
|
|
||||||
|
|
||||||
for(int k = j + 1; k < 23; k++)
|
|
||||||
{
|
|
||||||
int pattern = (1 << i) | (1 << j) | (1 << k);
|
|
||||||
ERROR_PATTERNS[getSyndrome(pattern)] = pattern;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int seed = 0; seed < 4096; seed++)
|
|
||||||
{
|
|
||||||
int pr = 16 * seed;
|
|
||||||
int sequence = 0;
|
|
||||||
|
|
||||||
for(int x = 0; x < 23; x++)
|
|
||||||
{
|
|
||||||
pr = (173 * pr + 13849) % 65536;
|
|
||||||
sequence = (sequence << 1) | (pr >= 32768 ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SCRAMBLE_SEQUENCES[seed] = sequence;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AMBEFrameValidator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Burst bit index for the voice frame bit index.
|
|
||||||
* @param frame index 0-2
|
|
||||||
* @param bit index 0-71 within the (interleaved) voice frame
|
|
||||||
* @return bit index within the 288-bit burst
|
|
||||||
*/
|
|
||||||
private static int getBurstIndex(int frame, int bit)
|
|
||||||
{
|
|
||||||
//Frame 2 is split around the 48-bit sync/embedded signalling field (burst bits 132 - 179)
|
|
||||||
if(frame == 1 && bit >= 36)
|
|
||||||
{
|
|
||||||
return FRAME_OFFSETS[frame] + bit + 48;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FRAME_OFFSETS[frame] + bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Golay(23,12) syndrome for the 23-bit word where the MSB (bit 22) is the first codeword bit.
|
|
||||||
*/
|
|
||||||
private static int getSyndrome(int word)
|
|
||||||
{
|
|
||||||
for(int bit = 22; bit >= 11; bit--)
|
|
||||||
{
|
|
||||||
if((word & (1 << bit)) != 0)
|
|
||||||
{
|
|
||||||
word ^= GOLAY_23_GENERATOR << (bit - 11);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the indexed bits from the message as an integer value, with the first index as the MSB.
|
|
||||||
*/
|
|
||||||
private static int getValue(BinaryMessage message, int[] indices)
|
|
||||||
{
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
for(int index : indices)
|
|
||||||
{
|
|
||||||
value = (value << 1) | (message.get(index) ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combined C0 and C1 bit error count for the voice frame, where C0 is decoded first so that the corrected C0 data
|
|
||||||
* bits can seed the C1 descrambling sequence. C0 errors include the extended Golay parity bit, so an even weight
|
|
||||||
* C0 error pattern (ie 4 errors) that Golay(23,12) mis-corrects is counted as 4 errors.
|
|
||||||
* @param message containing the 288-bit voice burst
|
|
||||||
* @param frame index 0-2
|
|
||||||
* @return combined C0 and C1 bit error count.
|
|
||||||
*/
|
|
||||||
public static int getErrorCount(BinaryMessage message, int frame)
|
|
||||||
{
|
|
||||||
int c0 = getValue(message, C0_INDICES[frame]);
|
|
||||||
int golay = c0 >>> 1;
|
|
||||||
int pattern = ERROR_PATTERNS[getSyndrome(golay)];
|
|
||||||
int corrected = golay ^ pattern;
|
|
||||||
int errors = Integer.bitCount(pattern);
|
|
||||||
|
|
||||||
//Extended Golay(24,12) parity bit provides even parity across the 24-bit codeword
|
|
||||||
if(((Integer.bitCount(corrected) + (c0 & 1)) & 1) != 0)
|
|
||||||
{
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Short circuit when C0 alone exceeds the max error count.
|
|
||||||
if(errors > MAX_ERRORS)
|
|
||||||
{
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
int c1 = getValue(message, C1_INDICES[frame]) ^ SCRAMBLE_SEQUENCES[corrected >>> 11];
|
|
||||||
return errors + Integer.bitCount(ERROR_PATTERNS[getSyndrome(c1)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if at least one of the three voice frames in the burst is valid, checking each frame in turn until a
|
|
||||||
* valid frame is found.
|
|
||||||
* @param message containing the 288-bit voice burst
|
|
||||||
* @return true if at least one voice frame has a combined C0 and C1 error count within the max error count.
|
|
||||||
*/
|
|
||||||
public static boolean isValid(BinaryMessage message)
|
|
||||||
{
|
|
||||||
for(int frame = 0; frame < 3; frame++)
|
|
||||||
{
|
|
||||||
if(getErrorCount(message, frame) <= MAX_ERRORS)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,10 +40,9 @@ public abstract class VoiceMessage extends DMRBurst
|
|||||||
public static int[] FRAME_1_IV_FRAGMENT = new int[]{95, 91, 87, 83};
|
public static int[] FRAME_1_IV_FRAGMENT = new int[]{95, 91, 87, 83};
|
||||||
public static int[] FRAME_2_IV_FRAGMENT = new int[]{215, 211, 207, 203};
|
public static int[] FRAME_2_IV_FRAGMENT = new int[]{215, 211, 207, 203};
|
||||||
public static int[] FRAME_3_IV_FRAGMENT = new int[]{287, 283, 279, 275};
|
public static int[] FRAME_3_IV_FRAGMENT = new int[]{287, 283, 279, 275};
|
||||||
private Boolean mValid;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR message frame. This message comprises a 24-bit prefix and a 264-bit message frame. Outbound base
|
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
|
||||||
* station frames transmit a Common Announcement Channel (CACH) in the 24-bit prefix, whereas Mobile inbound frames
|
* station frames transmit a Common Announcement Channel (CACH) in the 24-bit prefix, whereas Mobile inbound frames
|
||||||
* do not use the 24-bit prefix.
|
* do not use the 24-bit prefix.
|
||||||
*
|
*
|
||||||
@@ -74,8 +73,8 @@ public abstract class VoiceMessage extends DMRBurst
|
|||||||
{
|
{
|
||||||
frame_2[i] = getMessage().getByte(96 + i * 8);
|
frame_2[i] = getMessage().getByte(96 + i * 8);
|
||||||
}
|
}
|
||||||
// 4 bits and 4 bits. Note: mask the shifted (signed) byte to prevent sign extension into the upper 4 bits.
|
// 4 bits and 4 bits
|
||||||
frame_2[4] = (byte)((getMessage().getByte(128) & 0xF0) | ((getMessage().getByte(180) >> 4) & 0x0F));
|
frame_2[4] = (byte)((getMessage().getByte(128) & 0xF0) | (getMessage().getByte(180) >> 4));
|
||||||
// copy last 4 byte
|
// copy last 4 byte
|
||||||
for(int i = 0; i < 4; i++)
|
for(int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
@@ -110,20 +109,10 @@ public abstract class VoiceMessage extends DMRBurst
|
|||||||
return fragments;
|
return fragments;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the voice burst is valid, meaning that at least one of the three AMBE voice frames passes error
|
|
||||||
* detection (see AMBEFrameValidator). Frames are checked in turn until a valid frame is found and the result is
|
|
||||||
* cached.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid()
|
public boolean isValid()
|
||||||
{
|
{
|
||||||
if(mValid == null)
|
return true;
|
||||||
{
|
|
||||||
mValid = AMBEFrameValidator.isValid(getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mValid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCIn
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMessageUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMessageUpdate;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMotorolaGroupRegroupChannelGrant;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMotorolaGroupRegroupChannelGrant;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCProtectionParameterBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCStatusUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCStatusUpdate;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCTelephoneInterconnectChannelGrant;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCTelephoneInterconnectChannelGrant;
|
||||||
@@ -598,7 +599,6 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
|
|
||||||
//Network configuration messages
|
//Network configuration messages
|
||||||
case OSP_ADJACENT_STATUS_BROADCAST:
|
case OSP_ADJACENT_STATUS_BROADCAST:
|
||||||
case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN:
|
|
||||||
mNetworkConfigurationMonitor.process(ambtc);
|
mNetworkConfigurationMonitor.process(ambtc);
|
||||||
break;
|
break;
|
||||||
case OSP_NETWORK_STATUS_BROADCAST:
|
case OSP_NETWORK_STATUS_BROADCAST:
|
||||||
@@ -678,6 +678,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
mu.getShortDataMessage());
|
mu.getShortDataMessage());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN:
|
||||||
|
if(ambtc instanceof AMBTCProtectionParameterBroadcast ppb)
|
||||||
|
{
|
||||||
|
broadcastEvent(ambtc, DecodeEventType.RESPONSE, "USE ENCRYPTION " + ppb.getEncryptionKey() +
|
||||||
|
" OUTBOUND MI:" + ppb.getOutboundMessageIndicator() +
|
||||||
|
" INBOUND MI:" + ppb.getInboundMessageIndicator());
|
||||||
|
}
|
||||||
|
break;
|
||||||
case OSP_ROAMING_ADDRESS_UPDATE:
|
case OSP_ROAMING_ADDRESS_UPDATE:
|
||||||
broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.RESPONSE,
|
broadcastEvent(ambtc.getIdentifiers(), ambtc.getTimestamp(), DecodeEventType.RESPONSE,
|
||||||
"ROAMING ADDRESS UPDATE");
|
"ROAMING ADDRESS UPDATE");
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSeconda
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSystemServiceBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSystemServiceBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcastUncoordinated;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
|
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
|
||||||
@@ -199,7 +198,6 @@ public class P25P1NetworkConfigurationMonitor
|
|||||||
switch(ambtc.getHeader().getOpcode())
|
switch(ambtc.getHeader().getOpcode())
|
||||||
{
|
{
|
||||||
case OSP_ADJACENT_STATUS_BROADCAST:
|
case OSP_ADJACENT_STATUS_BROADCAST:
|
||||||
case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN:
|
|
||||||
if(ambtc instanceof AMBTCAdjacentStatusBroadcast)
|
if(ambtc instanceof AMBTCAdjacentStatusBroadcast)
|
||||||
{
|
{
|
||||||
AMBTCAdjacentStatusBroadcast aasb = (AMBTCAdjacentStatusBroadcast)ambtc;
|
AMBTCAdjacentStatusBroadcast aasb = (AMBTCAdjacentStatusBroadcast)ambtc;
|
||||||
@@ -504,24 +502,14 @@ public class P25P1NetworkConfigurationMonitor
|
|||||||
if(mAMBTCNeighborSites.containsKey(site))
|
if(mAMBTCNeighborSites.containsKey(site))
|
||||||
{
|
{
|
||||||
AMBTCAdjacentStatusBroadcast ambtc = mAMBTCNeighborSites.get(site);
|
AMBTCAdjacentStatusBroadcast ambtc = mAMBTCNeighborSites.get(site);
|
||||||
sb.append(" ");
|
sb.append(" SYSTEM:").append(format(ambtc.getSystem(), 3));
|
||||||
if(ambtc instanceof AMBTCAdjacentStatusBroadcastUncoordinated unc)
|
|
||||||
{
|
|
||||||
sb.append("WACN:").append(unc.getWacn()).append(" ");
|
|
||||||
}
|
|
||||||
sb.append("SYSTEM:").append(format(ambtc.getSystem(), 3));
|
|
||||||
sb.append(" NAC:").append(format(ambtc.getNAC(), 3));
|
sb.append(" NAC:").append(format(ambtc.getNAC(), 3));
|
||||||
sb.append(" RFSS:").append(format(ambtc.getRfss(), 2));
|
sb.append(" RFSS:").append(format(ambtc.getRfss(), 2));
|
||||||
sb.append(" SITE:").append(format(ambtc.getSite(), 2));
|
sb.append(" SITE:").append(format(ambtc.getSite(), 2));
|
||||||
sb.append(" LRA:").append(format(ambtc.getLocationRegistrationArea(), 2));
|
sb.append(" LRA:").append(format(ambtc.getLocationRegistrationArea(), 2));
|
||||||
sb.append(" CHANNEL:").append(ambtc.getChannel());
|
sb.append(" CHANNEL:").append(ambtc.getChannel());
|
||||||
sb.append(" DOWNLINK:").append(ambtc.getChannel().getDownlinkFrequency());
|
sb.append(" DOWNLINK:").append(ambtc.getChannel().getDownlinkFrequency());
|
||||||
sb.append(" UPLINK:").append(ambtc.getChannel().getUplinkFrequency());
|
sb.append(" UPLINK:").append(ambtc.getChannel().getUplinkFrequency()).append("\n");
|
||||||
if(ambtc instanceof AMBTCAdjacentStatusBroadcastUncoordinated unc)
|
|
||||||
{
|
|
||||||
sb.append(" SERVICES:").append(unc.getSystemServiceClass());
|
|
||||||
}
|
|
||||||
sb.append("\n");
|
|
||||||
}
|
}
|
||||||
if(mLCNeighborSites.containsKey(site))
|
if(mLCNeighborSites.containsKey(site))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUn
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitToUnitAnswerResponse;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitToUnitAnswerResponse;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceRequest;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.isp.AMBTCUnitToUnitVoiceServiceRequest;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCAdjacentStatusBroadcastUncoordinated;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCCallAlert;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCCallAlert;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCFrequencyBandUpdateTDMA;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCFrequencyBandUpdateTDMA;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCGroupAffiliationQuery;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCGroupAffiliationQuery;
|
||||||
@@ -48,6 +47,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCIn
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMessageUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMessageUpdate;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMotorolaGroupRegroupChannelGrant;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCMotorolaGroupRegroupChannelGrant;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNetworkStatusBroadcast;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCProtectionParameterBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRoamingAddressResponse;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRoamingAddressResponse;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRoamingAddressUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRoamingAddressUpdate;
|
||||||
@@ -337,7 +337,7 @@ public class PDUMessageFactory
|
|||||||
case OSP_NETWORK_STATUS_BROADCAST:
|
case OSP_NETWORK_STATUS_BROADCAST:
|
||||||
return new AMBTCNetworkStatusBroadcast(pduSequence, nac, timestamp);
|
return new AMBTCNetworkStatusBroadcast(pduSequence, nac, timestamp);
|
||||||
case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN:
|
case OSP_ADJACENT_STATUS_BROADCAST_UNCOORDINATED_BAND_PLAN:
|
||||||
return new AMBTCAdjacentStatusBroadcastUncoordinated(pduSequence, nac, timestamp);
|
return new AMBTCProtectionParameterBroadcast(pduSequence, nac, timestamp);
|
||||||
case OSP_RFSS_STATUS_BROADCAST:
|
case OSP_RFSS_STATUS_BROADCAST:
|
||||||
return new AMBTCRFSSStatusBroadcast(pduSequence, nac, timestamp);
|
return new AMBTCRFSSStatusBroadcast(pduSequence, nac, timestamp);
|
||||||
case OSP_ROAMING_ADDRESS_UPDATE:
|
case OSP_ROAMING_ADDRESS_UPDATE:
|
||||||
|
|||||||
@@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp;
|
|
||||||
|
|
||||||
import io.github.dsheirer.channel.IBandChannelDescriptor;
|
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.APCO25Wacn;
|
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel;
|
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25ExplicitChannel;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.block.UnconfirmedDataBlock;
|
|
||||||
import io.github.dsheirer.module.decode.p25.reference.SystemServiceClass;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjacent Status Broadcast - Uncoordinated Band Plan (opcode 0x3E), extended/multi-block (AMBTC) format.
|
|
||||||
*
|
|
||||||
* Announces a neighbor site that may belong to a different WACN/system (and band plan) than the current site.
|
|
||||||
* Opcode 0x3E was previously defined as Protection Parameter Broadcast; current TIA-102.AABC revisions reassign it.
|
|
||||||
*
|
|
||||||
* Header: LRA (24-31), flags (32-35), SYSTEM (36-47), RFSS (64-71), SITE (72-79) -- same as opcode 0x3C
|
|
||||||
* Block 0: TRANSMIT CHANNEL (0-15), RECEIVE CHANNEL (16-31), SYSTEM SERVICE CLASS (32-39), WACN (40-59)
|
|
||||||
*
|
|
||||||
* A receive channel of 0xFFFF indicates that no separate receive channel is specified.
|
|
||||||
*/
|
|
||||||
public class AMBTCAdjacentStatusBroadcastUncoordinated extends AMBTCAdjacentStatusBroadcast
|
|
||||||
{
|
|
||||||
private static final int[] BLOCK_0_DOWNLINK_FREQUENCY_BAND = {0, 1, 2, 3};
|
|
||||||
private static final int[] BLOCK_0_DOWNLINK_CHANNEL_NUMBER = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
|
||||||
private static final int[] BLOCK_0_UPLINK_CHANNEL = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
|
||||||
private static final int[] BLOCK_0_UPLINK_FREQUENCY_BAND = {16, 17, 18, 19};
|
|
||||||
private static final int[] BLOCK_0_UPLINK_CHANNEL_NUMBER = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
|
||||||
private static final int[] BLOCK_0_SYSTEM_SERVICE_CLASS = {32, 33, 34, 35, 36, 37, 38, 39};
|
|
||||||
private static final int[] BLOCK_0_WACN = {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
|
|
||||||
58, 59};
|
|
||||||
private static final int UNSPECIFIED_CHANNEL = 0xFFFF;
|
|
||||||
|
|
||||||
private IBandChannelDescriptor mChannel;
|
|
||||||
private Identifier mWacn;
|
|
||||||
private SystemServiceClass mSystemServiceClass;
|
|
||||||
|
|
||||||
public AMBTCAdjacentStatusBroadcastUncoordinated(PDUSequence PDUSequence, int nac, long timestamp)
|
|
||||||
{
|
|
||||||
super(PDUSequence, nac, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(getMessageStub());
|
|
||||||
sb.append(" WACN:").append(getWacn());
|
|
||||||
sb.append(" SYSTEM:").append(getSystem());
|
|
||||||
sb.append(" LRA:").append(getLocationRegistrationArea());
|
|
||||||
sb.append(" RFSS:").append(getRfss());
|
|
||||||
sb.append(" SITE:").append(getSite());
|
|
||||||
sb.append(" CHANNEL:").append(getChannel());
|
|
||||||
sb.append(" SERVICES:").append(getSystemServiceClass());
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WACN of the neighbor site
|
|
||||||
*/
|
|
||||||
public Identifier getWacn()
|
|
||||||
{
|
|
||||||
if(mWacn == null && hasDataBlock(0))
|
|
||||||
{
|
|
||||||
mWacn = APCO25Wacn.create(getDataBlock(0).getMessage().getInt(BLOCK_0_WACN));
|
|
||||||
}
|
|
||||||
|
|
||||||
return mWacn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Services offered by the neighbor site
|
|
||||||
*/
|
|
||||||
public SystemServiceClass getSystemServiceClass()
|
|
||||||
{
|
|
||||||
if(mSystemServiceClass == null)
|
|
||||||
{
|
|
||||||
mSystemServiceClass = new SystemServiceClass(hasDataBlock(0) ?
|
|
||||||
getDataBlock(0).getMessage().getInt(BLOCK_0_SYSTEM_SERVICE_CLASS) : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mSystemServiceClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasUplinkChannel()
|
|
||||||
{
|
|
||||||
return hasDataBlock(0) && getDataBlock(0).getMessage().getInt(BLOCK_0_UPLINK_CHANNEL) != UNSPECIFIED_CHANNEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isExtendedChannel()
|
|
||||||
{
|
|
||||||
return hasUplinkChannel() && super.isExtendedChannel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBandChannelDescriptor getChannel()
|
|
||||||
{
|
|
||||||
if(mChannel == null)
|
|
||||||
{
|
|
||||||
if(hasDataBlock(0))
|
|
||||||
{
|
|
||||||
UnconfirmedDataBlock block = getDataBlock(0);
|
|
||||||
|
|
||||||
if(isExtendedChannel())
|
|
||||||
{
|
|
||||||
mChannel = APCO25ExplicitChannel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND),
|
|
||||||
block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER),
|
|
||||||
block.getMessage().getInt(BLOCK_0_UPLINK_FREQUENCY_BAND),
|
|
||||||
block.getMessage().getInt(BLOCK_0_UPLINK_CHANNEL_NUMBER));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mChannel = APCO25Channel.create(block.getMessage().getInt(BLOCK_0_DOWNLINK_FREQUENCY_BAND),
|
|
||||||
block.getMessage().getInt(BLOCK_0_DOWNLINK_CHANNEL_NUMBER));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mChannel = APCO25Channel.create(-1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mChannel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,7 +32,6 @@ public enum TunerClass
|
|||||||
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro+" ),
|
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro+" ),
|
||||||
HACKRF("HackRF" ),
|
HACKRF("HackRF" ),
|
||||||
HYDRASDR("HydraSDR"),
|
HYDRASDR("HydraSDR"),
|
||||||
LIMESDR("LimeSDR"),
|
|
||||||
RTL2832("RTL-2832"),
|
RTL2832("RTL-2832"),
|
||||||
RSP("RSP"),
|
RSP("RSP"),
|
||||||
TEST_TUNER("Test"),
|
TEST_TUNER("Test"),
|
||||||
@@ -57,7 +56,7 @@ public enum TunerClass
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final EnumSet<TunerClass> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY, AIRSPY_HF, BLADERF, HACKRF,
|
public static final EnumSet<TunerClass> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY, AIRSPY_HF, BLADERF, HACKRF,
|
||||||
HYDRASDR, LIMESDR, RTL2832, FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
|
HYDRASDR, RTL2832, FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
|
||||||
|
|
||||||
public static final EnumSet<TunerClass> FUNCUBE_TUNERS = EnumSet.of(FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
|
public static final EnumSet<TunerClass> FUNCUBE_TUNERS = EnumSet.of(FUNCUBE_DONGLE_PRO, FUNCUBE_DONGLE_PRO_PLUS);
|
||||||
|
|
||||||
@@ -147,9 +146,6 @@ public enum TunerClass
|
|||||||
return BLADERF;
|
return BLADERF;
|
||||||
case 0x38AF0001: //HYDRASDR RFONE (official VID 38af, PID 0001)
|
case 0x38AF0001: //HYDRASDR RFONE (official VID 38af, PID 0001)
|
||||||
return HYDRASDR;
|
return HYDRASDR;
|
||||||
case 0x1D506108: //LIMESDR-USB
|
|
||||||
case 0x0403601F: //LIMESDR MINI (FTDI FT601)
|
|
||||||
return LIMESDR;
|
|
||||||
case 0x1D5060A1:
|
case 0x1D5060A1:
|
||||||
return AIRSPY;
|
return AIRSPY;
|
||||||
case 0x03EB800C:
|
case 0x03EB800C:
|
||||||
|
|||||||
@@ -54,10 +54,6 @@ import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTuner;
|
|||||||
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration;
|
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController;
|
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerController;
|
||||||
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerEditor;
|
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerEditor;
|
||||||
import io.github.dsheirer.source.tuner.limesdr.LimeSdrTuner;
|
|
||||||
import io.github.dsheirer.source.tuner.limesdr.LimeSdrTunerConfiguration;
|
|
||||||
import io.github.dsheirer.source.tuner.limesdr.LimeSdrTunerController;
|
|
||||||
import io.github.dsheirer.source.tuner.limesdr.LimeSdrTunerEditor;
|
|
||||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
|
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
|
||||||
@@ -317,8 +313,6 @@ public class TunerFactory
|
|||||||
return new HackRFTuner(new HackRFTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
return new HackRFTuner(new HackRFTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||||
case HYDRASDR:
|
case HYDRASDR:
|
||||||
return new HydraSdrTuner(new HydraSdrTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
return new HydraSdrTuner(new HydraSdrTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||||
case LIMESDR:
|
|
||||||
return new LimeSdrTuner(new LimeSdrTunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
|
||||||
case RTL2832:
|
case RTL2832:
|
||||||
return new RTL2832Tuner(new RTL2832TunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
return new RTL2832Tuner(new RTL2832TunerController(bus, portAddress, tunerErrorListener), tunerErrorListener, channelizerType);
|
||||||
default:
|
default:
|
||||||
@@ -358,8 +352,6 @@ public class TunerFactory
|
|||||||
return new HackRFTunerConfiguration(uniqueID);
|
return new HackRFTunerConfiguration(uniqueID);
|
||||||
case HYDRASDR_R828D:
|
case HYDRASDR_R828D:
|
||||||
return new HydraSdrTunerConfiguration(uniqueID);
|
return new HydraSdrTunerConfiguration(uniqueID);
|
||||||
case LIMESDR:
|
|
||||||
return new LimeSdrTunerConfiguration(uniqueID);
|
|
||||||
case RAFAELMICRO_R820T:
|
case RAFAELMICRO_R820T:
|
||||||
return new R820TTunerConfiguration(uniqueID);
|
return new R820TTunerConfiguration(uniqueID);
|
||||||
case RAFAELMICRO_R828D:
|
case RAFAELMICRO_R828D:
|
||||||
@@ -407,8 +399,6 @@ public class TunerFactory
|
|||||||
return new HackRFTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
return new HackRFTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||||
case HYDRASDR:
|
case HYDRASDR:
|
||||||
return new HydraSdrTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
return new HydraSdrTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||||
case LIMESDR:
|
|
||||||
return new LimeSdrTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
|
||||||
case RSP:
|
case RSP:
|
||||||
if(discoveredTuner instanceof DiscoveredRspTuner discoveredRspTuner)
|
if(discoveredTuner instanceof DiscoveredRspTuner discoveredRspTuner)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ public enum TunerType
|
|||||||
HACKRF_JAWBREAKER("Jawbreaker"),
|
HACKRF_JAWBREAKER("Jawbreaker"),
|
||||||
HACKRF_RAD1O("RAD1O"),
|
HACKRF_RAD1O("RAD1O"),
|
||||||
HYDRASDR_R828D("R828D"),
|
HYDRASDR_R828D("R828D"),
|
||||||
LIMESDR("LimeSDR"),
|
|
||||||
RAFAELMICRO_R820T("R820T"),
|
RAFAELMICRO_R820T("R820T"),
|
||||||
RAFAELMICRO_R828D("R828D"),
|
RAFAELMICRO_R828D("R828D"),
|
||||||
RSP_1("RSP1"),
|
RSP_1("RSP1"),
|
||||||
@@ -77,7 +76,7 @@ public enum TunerType
|
|||||||
* Supported USB tuner types
|
* Supported USB tuner types
|
||||||
*/
|
*/
|
||||||
public static final EnumSet<TunerType> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY_R820T, BLADERF, ELONICS_E4000, FITIPOWER_FC0012,
|
public static final EnumSet<TunerType> SUPPORTED_USB_TUNERS = EnumSet.of(AIRSPY_R820T, BLADERF, ELONICS_E4000, FITIPOWER_FC0012,
|
||||||
HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HACKRF_PRO, HYDRASDR_R828D, LIMESDR, RAFAELMICRO_R820T);
|
HACKRF_JAWBREAKER, HACKRF_RAD1O, HACKRF_ONE, HACKRF_PRO, HYDRASDR_R828D, RAFAELMICRO_R820T);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supported sound card tuner types
|
* Supported sound card tuner types
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ public class AirspyTunerConfiguration extends TunerConfiguration
|
|||||||
private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT;
|
private int mLNAGain = AirspyTunerController.LNA_GAIN_DEFAULT;
|
||||||
private boolean mMixerAGC = false;
|
private boolean mMixerAGC = false;
|
||||||
private boolean mLNAAGC = false;
|
private boolean mLNAAGC = false;
|
||||||
private boolean mBiasT = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor for JAXB
|
* Default constructor for JAXB
|
||||||
@@ -130,15 +129,4 @@ public class AirspyTunerConfiguration extends TunerConfiguration
|
|||||||
{
|
{
|
||||||
mLNAAGC = enabled;
|
mLNAAGC = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "bias_t")
|
|
||||||
public boolean isBiasT()
|
|
||||||
{
|
|
||||||
return mBiasT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBiasT(boolean enabled)
|
|
||||||
{
|
|
||||||
mBiasT = enabled;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,19 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
|||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.UsbDeviceLookup;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.usb4java.Context;
|
||||||
|
import org.usb4java.Device;
|
||||||
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
import org.usb4java.DeviceHandle;
|
||||||
|
import org.usb4java.DeviceList;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Airspy tuner controller, implemented with the jAirspy library (libairspy).
|
* Airspy tuner controller, implemented with the jAirspy library (libairspy).
|
||||||
@@ -177,22 +183,13 @@ public class AirspyTunerController extends TunerController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns off the bias-T and closes the device, which also stops streaming.
|
* Closes the device, which also stops streaming.
|
||||||
* @param device to close, or null
|
* @param device to close, or null
|
||||||
*/
|
*/
|
||||||
private static void closeDevice(AirspyDevice device)
|
private static void closeDevice(AirspyDevice device)
|
||||||
{
|
{
|
||||||
if(device != null)
|
if(device != null)
|
||||||
{
|
{
|
||||||
try
|
|
||||||
{
|
|
||||||
device.setBiasTee(false);
|
|
||||||
}
|
|
||||||
catch(AirspyException | RuntimeException e)
|
|
||||||
{
|
|
||||||
mLog.warn("Error turning off Airspy bias-T before closing the device - " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
device.close();
|
device.close();
|
||||||
@@ -206,17 +203,98 @@ public class AirspyTunerController extends TunerController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the serial number of the device at this controller's USB bus and port, since libairspy opens a specific
|
* Reads the serial number of the device at this controller's USB bus and port, since libairspy opens a specific
|
||||||
* device by serial number. The USB serial string is formatted as: AIRSPY SN:0123456789ABCDEF
|
* device by serial number. The device is closed again before returning so that libairspy can open it.
|
||||||
* @return serial number
|
* @return serial number
|
||||||
* @throws SourceException if the device isn't found or can't be accessed.
|
* @throws SourceException if the device isn't found or can't be accessed.
|
||||||
*/
|
*/
|
||||||
private long readSerialNumber() throws SourceException
|
private long readSerialNumber() throws SourceException
|
||||||
{
|
{
|
||||||
String serial = UsbDeviceLookup.readSerialNumber(mBus, mPortAddress);
|
Context context = new Context();
|
||||||
|
int status = LibUsb.init(context);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Long.parseUnsignedLong(serial.substring(serial.lastIndexOf(':') + 1).trim(), 16);
|
DeviceList deviceList = new DeviceList();
|
||||||
|
int count = LibUsb.getDeviceList(context, deviceList);
|
||||||
|
|
||||||
|
if(count < 0)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(Device device : deviceList)
|
||||||
|
{
|
||||||
|
if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 &&
|
||||||
|
mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device)))
|
||||||
|
{
|
||||||
|
return readSerialNumber(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.freeDeviceList(deviceList, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.exit(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the serial number from the device's USB serial string, formatted as: AIRSPY SN:0123456789ABCDEF
|
||||||
|
* @param device to read
|
||||||
|
* @return serial number
|
||||||
|
* @throws SourceException if the device can't be opened or the serial string isn't valid.
|
||||||
|
*/
|
||||||
|
private static long readSerialNumber(Device device) throws SourceException
|
||||||
|
{
|
||||||
|
DeviceDescriptor descriptor = new DeviceDescriptor();
|
||||||
|
int status = LibUsb.getDeviceDescriptor(device, descriptor);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceHandle handle = new DeviceHandle();
|
||||||
|
status = LibUsb.open(device, handle);
|
||||||
|
|
||||||
|
if(status == LibUsb.ERROR_ACCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules");
|
||||||
|
throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules");
|
||||||
|
}
|
||||||
|
else if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Can't open USB tuner - check driver or Linux udev rules");
|
||||||
|
throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
String serial;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serial = LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.close(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Long.parseUnsignedLong(serial.substring(serial.lastIndexOf(':') + 1), 16);
|
||||||
}
|
}
|
||||||
catch(NullPointerException | NumberFormatException e)
|
catch(NullPointerException | NumberFormatException e)
|
||||||
{
|
{
|
||||||
@@ -384,7 +462,6 @@ public class AirspyTunerController extends TunerController
|
|||||||
setLNAGain(config.getLNAGain());
|
setLNAGain(config.getLNAGain());
|
||||||
setMixerAGC(config.isMixerAGC());
|
setMixerAGC(config.isMixerAGC());
|
||||||
setLNAAGC(config.isLNAAGC());
|
setLNAAGC(config.isLNAAGC());
|
||||||
setBiasT(config.isBiasT());
|
|
||||||
|
|
||||||
//Set the gain mode last, so custom values are already set, and linearity and sensitivity modes will
|
//Set the gain mode last, so custom values are already set, and linearity and sensitivity modes will
|
||||||
//automatically override the custom values.
|
//automatically override the custom values.
|
||||||
@@ -543,17 +620,6 @@ public class AirspyTunerController extends TunerController
|
|||||||
execute(device -> device.setLnaAgc(enabled));
|
execute(device -> device.setLnaAgc(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables/disables the Bias-T (RF bias tee) power output for active antennas
|
|
||||||
*
|
|
||||||
* @param enabled true to enable Bias-T, false to disable
|
|
||||||
* @throws SourceException on error
|
|
||||||
*/
|
|
||||||
public void setBiasT(boolean enabled) throws SourceException
|
|
||||||
{
|
|
||||||
execute(device -> device.setBiasTee(enabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGain(Gain gain) throws SourceException
|
public void setGain(Gain gain) throws SourceException
|
||||||
{
|
{
|
||||||
if(gain != Gain.CUSTOM)
|
if(gain != Gain.CUSTOM)
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ public class AirspyTunerEditor extends TunerEditor<AirspyTuner, AirspyTunerConfi
|
|||||||
private JLabel mMixerGainValueLabel;
|
private JLabel mMixerGainValueLabel;
|
||||||
private JCheckBox mLNAAGCCheckBox;
|
private JCheckBox mLNAAGCCheckBox;
|
||||||
private JCheckBox mMixerAGCCheckBox;
|
private JCheckBox mMixerAGCCheckBox;
|
||||||
private JCheckBox mBiasTCheckBox;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -117,8 +116,6 @@ public class AirspyTunerEditor extends TunerEditor<AirspyTuner, AirspyTunerConfi
|
|||||||
getFrequencyPanel().updateControls();
|
getFrequencyPanel().updateControls();
|
||||||
getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLockedSampleRate());
|
getSampleRateCombo().setEnabled(hasTuner() && !getTuner().getTunerController().isLockedSampleRate());
|
||||||
getTunerInfoButton().setEnabled(hasTuner());
|
getTunerInfoButton().setEnabled(hasTuner());
|
||||||
getBiasTCheckBox().setEnabled(hasTuner());
|
|
||||||
getBiasTCheckBox().setSelected(hasTuner() && hasConfiguration() && getConfiguration().isBiasT());
|
|
||||||
updateGainComponents((hasTuner() && hasConfiguration()) ? getConfiguration().getGain() : null);
|
updateGainComponents((hasTuner() && hasConfiguration()) ? getConfiguration().getGain() : null);
|
||||||
|
|
||||||
if(hasTuner())
|
if(hasTuner())
|
||||||
@@ -162,9 +159,6 @@ public class AirspyTunerEditor extends TunerEditor<AirspyTuner, AirspyTunerConfi
|
|||||||
add(new JLabel("Sample Rate:"));
|
add(new JLabel("Sample Rate:"));
|
||||||
add(getSampleRateCombo(), "wrap");
|
add(getSampleRateCombo(), "wrap");
|
||||||
|
|
||||||
add(new JLabel(""));
|
|
||||||
add(getBiasTCheckBox(), "wrap");
|
|
||||||
|
|
||||||
add(new JSeparator(), "span,growx,push");
|
add(new JSeparator(), "span,growx,push");
|
||||||
add(new JLabel("Gain Control"), "wrap");
|
add(new JLabel("Gain Control"), "wrap");
|
||||||
|
|
||||||
@@ -188,37 +182,6 @@ public class AirspyTunerEditor extends TunerEditor<AirspyTuner, AirspyTunerConfi
|
|||||||
add(getLNAGainValueLabel());
|
add(getLNAGainValueLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checkbox to enable/disable Bias-T power for active antennas
|
|
||||||
*/
|
|
||||||
private JCheckBox getBiasTCheckBox()
|
|
||||||
{
|
|
||||||
if(mBiasTCheckBox == null)
|
|
||||||
{
|
|
||||||
mBiasTCheckBox = new JCheckBox("Bias-T");
|
|
||||||
mBiasTCheckBox.setEnabled(false);
|
|
||||||
mBiasTCheckBox.setToolTipText("Enable Bias-T power output for active antennas");
|
|
||||||
mBiasTCheckBox.addActionListener(e ->
|
|
||||||
{
|
|
||||||
if(hasTuner() && !isLoading())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getTuner().getController().setBiasT(mBiasTCheckBox.isSelected());
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
catch(Exception e1)
|
|
||||||
{
|
|
||||||
mLog.error("Error setting Bias-T", e1);
|
|
||||||
JOptionPane.showMessageDialog(mBiasTCheckBox, "Couldn't set Bias-T: " + e1.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mBiasTCheckBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JCheckBox getLNAAGCCheckBox()
|
private JCheckBox getLNAAGCCheckBox()
|
||||||
{
|
{
|
||||||
if(mLNAAGCCheckBox == null)
|
if(mLNAAGCCheckBox == null)
|
||||||
@@ -644,7 +607,6 @@ public class AirspyTunerEditor extends TunerEditor<AirspyTuner, AirspyTunerConfi
|
|||||||
getConfiguration().setLNAGain(getLNAGainSlider().getValue());
|
getConfiguration().setLNAGain(getLNAGainSlider().getValue());
|
||||||
getConfiguration().setMixerAGC(getMixerAGCCheckBox().isSelected());
|
getConfiguration().setMixerAGC(getMixerAGCCheckBox().isSelected());
|
||||||
getConfiguration().setLNAAGC(getLNAAGCCheckBox().isSelected());
|
getConfiguration().setLNAAGC(getLNAAGCCheckBox().isSelected());
|
||||||
getConfiguration().setBiasT(getBiasTCheckBox().isSelected());
|
|
||||||
saveConfiguration();
|
saveConfiguration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ public class AirspyHfTunerConfiguration extends TunerConfiguration
|
|||||||
private boolean mAgc;
|
private boolean mAgc;
|
||||||
private boolean mLna;
|
private boolean mLna;
|
||||||
private int mAttenuation;
|
private int mAttenuation;
|
||||||
private boolean mBiasT;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor for JAXB
|
* Default constructor for JAXB
|
||||||
@@ -115,25 +114,6 @@ public class AirspyHfTunerConfiguration extends TunerConfiguration
|
|||||||
mLna = lna;
|
mLna = lna;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the Bias-T power output is enabled. Only applies to devices whose firmware supports a bias-T.
|
|
||||||
* @return bias-T enabled state
|
|
||||||
*/
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "bias_t")
|
|
||||||
public boolean isBiasT()
|
|
||||||
{
|
|
||||||
return mBiasT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Bias-T power output enabled state
|
|
||||||
* @param enabled true to enable
|
|
||||||
*/
|
|
||||||
public void setBiasT(boolean enabled)
|
|
||||||
{
|
|
||||||
mBiasT = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attenuation setting.
|
* Attenuation setting.
|
||||||
* @return attenuation setting index value.
|
* @return attenuation setting index value.
|
||||||
|
|||||||
@@ -32,12 +32,18 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
|||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.UsbDeviceLookup;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.usb4java.Context;
|
||||||
|
import org.usb4java.Device;
|
||||||
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
import org.usb4java.DeviceHandle;
|
||||||
|
import org.usb4java.DeviceList;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Airspy HF+ Dual Port, HF+ Discovery and Ranger tuner controller, implemented with the jAirspyHF library
|
* Airspy HF+ Dual Port, HF+ Discovery and Ranger tuner controller, implemented with the jAirspyHF library
|
||||||
@@ -67,8 +73,6 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
private Attenuation mAttenuation = Attenuation.A0;
|
private Attenuation mAttenuation = Attenuation.A0;
|
||||||
private boolean mAgcEnabled;
|
private boolean mAgcEnabled;
|
||||||
private boolean mLnaEnabled;
|
private boolean mLnaEnabled;
|
||||||
private boolean mBiasTSupported;
|
|
||||||
private boolean mBiasTEnabled;
|
|
||||||
private volatile float mSamplesPerMillisecond = DEFAULT_SAMPLE_RATE.getSampleRate() / 1000.0f;
|
private volatile float mSamplesPerMillisecond = DEFAULT_SAMPLE_RATE.getSampleRate() / 1000.0f;
|
||||||
private volatile int mBufferSampleCount = DEFAULT_BUFFER_SAMPLE_COUNT;
|
private volatile int mBufferSampleCount = DEFAULT_BUFFER_SAMPLE_COUNT;
|
||||||
private boolean mStreaming = false;
|
private boolean mStreaming = false;
|
||||||
@@ -112,8 +116,6 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mBiasTSupported = false;
|
|
||||||
mBiasTEnabled = false;
|
|
||||||
mDevice = AirspyHf.open(serialNumber);
|
mDevice = AirspyHf.open(serialNumber);
|
||||||
|
|
||||||
BoardInfo boardInfo = mDevice.boardInfo();
|
BoardInfo boardInfo = mDevice.boardInfo();
|
||||||
@@ -137,8 +139,6 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
}
|
}
|
||||||
|
|
||||||
mAvailableSampleRates = Collections.unmodifiableList(sampleRates);
|
mAvailableSampleRates = Collections.unmodifiableList(sampleRates);
|
||||||
mBiasTSupported = detectBiasTSupport(mDevice);
|
|
||||||
mBiasTEnabled = false;
|
|
||||||
|
|
||||||
//Force the sample rate to be applied, since the rate discovery leaves the device at the last rate.
|
//Force the sample rate to be applied, since the rate discovery leaves the device at the last rate.
|
||||||
mCurrentSampleRate = null;
|
mCurrentSampleRate = null;
|
||||||
@@ -161,7 +161,6 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
AirspyHfDevice device;
|
AirspyHfDevice device;
|
||||||
boolean biasTEnabled;
|
|
||||||
|
|
||||||
getLock().lock();
|
getLock().lock();
|
||||||
|
|
||||||
@@ -170,8 +169,6 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
device = mDevice;
|
device = mDevice;
|
||||||
mDevice = null;
|
mDevice = null;
|
||||||
mStreaming = false;
|
mStreaming = false;
|
||||||
biasTEnabled = mBiasTEnabled;
|
|
||||||
mBiasTEnabled = false;
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -179,58 +176,9 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
}
|
}
|
||||||
|
|
||||||
mNativeBufferBroadcaster.clear();
|
mNativeBufferBroadcaster.clear();
|
||||||
|
|
||||||
if(device != null && biasTEnabled)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
device.setBiasTee(0);
|
|
||||||
}
|
|
||||||
catch(AirspyHfException | RuntimeException e)
|
|
||||||
{
|
|
||||||
mLog.warn("Error turning off Airspy HF bias-T before closing the device - " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeDevice(device);
|
closeDevice(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detects if the device firmware supports a bias-T. Firmware without bias-T support rejects the bias-T commands.
|
|
||||||
* Some firmware (HF+ Discovery R5.0.1) rejects the bias-T count request, so this falls back to turning the bias-T
|
|
||||||
* off, which is a safe probe since the bias-T should be off at startup anyway.
|
|
||||||
* @param device to probe
|
|
||||||
* @return true if the device accepts bias-T commands
|
|
||||||
*/
|
|
||||||
private static boolean detectBiasTSupport(AirspyHfDevice device)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(device.biasTeeCount() > 0)
|
|
||||||
{
|
|
||||||
device.setBiasTee(0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch(AirspyHfException | RuntimeException e)
|
|
||||||
{
|
|
||||||
//Bias-T count not supported - fall through and probe with a bias-T off command.
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
device.setBiasTee(0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch(AirspyHfException | RuntimeException e)
|
|
||||||
{
|
|
||||||
mLog.debug("Airspy HF firmware doesn't support bias-T - " + e.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the device, which also stops streaming.
|
* Closes the device, which also stops streaming.
|
||||||
* @param device to close, or null
|
* @param device to close, or null
|
||||||
@@ -252,13 +200,94 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the serial number of the device at this controller's USB bus and port, since libairspyhf opens a specific
|
* Reads the serial number of the device at this controller's USB bus and port, since libairspyhf opens a specific
|
||||||
* device by serial number. The USB serial string is formatted as: AIRSPYHF SN:0123456789ABCDEF
|
* device by serial number. The device is closed again before returning so that libairspyhf can open it.
|
||||||
* @return serial number
|
* @return serial number
|
||||||
* @throws SourceException if the device isn't found or can't be accessed.
|
* @throws SourceException if the device isn't found or can't be accessed.
|
||||||
*/
|
*/
|
||||||
private long readSerialNumber() throws SourceException
|
private long readSerialNumber() throws SourceException
|
||||||
{
|
{
|
||||||
String serial = UsbDeviceLookup.readSerialNumber(mBus, mPortAddress);
|
Context context = new Context();
|
||||||
|
int status = LibUsb.init(context);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DeviceList deviceList = new DeviceList();
|
||||||
|
int count = LibUsb.getDeviceList(context, deviceList);
|
||||||
|
|
||||||
|
if(count < 0)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(Device device : deviceList)
|
||||||
|
{
|
||||||
|
if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 &&
|
||||||
|
mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device)))
|
||||||
|
{
|
||||||
|
return readSerialNumber(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.freeDeviceList(deviceList, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.exit(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the serial number from the device's USB serial string, formatted as: AIRSPYHF SN:0123456789ABCDEF
|
||||||
|
* @param device to read
|
||||||
|
* @return serial number
|
||||||
|
* @throws SourceException if the device can't be opened or the serial string isn't valid.
|
||||||
|
*/
|
||||||
|
private static long readSerialNumber(Device device) throws SourceException
|
||||||
|
{
|
||||||
|
DeviceDescriptor descriptor = new DeviceDescriptor();
|
||||||
|
int status = LibUsb.getDeviceDescriptor(device, descriptor);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceHandle handle = new DeviceHandle();
|
||||||
|
status = LibUsb.open(device, handle);
|
||||||
|
|
||||||
|
if(status == LibUsb.ERROR_ACCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules");
|
||||||
|
throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules");
|
||||||
|
}
|
||||||
|
else if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Can't open USB tuner - check driver or Linux udev rules");
|
||||||
|
throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
String serial;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serial = LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.close(handle);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -419,11 +448,6 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
setLna(airspyConfig.isLna());
|
setLna(airspyConfig.isLna());
|
||||||
setAttenuation(airspyConfig.getAttenuation());
|
setAttenuation(airspyConfig.getAttenuation());
|
||||||
setAgc(airspyConfig.isAgc());
|
setAgc(airspyConfig.isAgc());
|
||||||
|
|
||||||
if(isBiasTSupported())
|
|
||||||
{
|
|
||||||
setBiasT(airspyConfig.isBiasT());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch(SourceException se)
|
catch(SourceException se)
|
||||||
{
|
{
|
||||||
@@ -646,38 +670,4 @@ public class AirspyHfTunerController extends TunerController
|
|||||||
execute(device -> device.setAttenuationStep(attenuation.getValue()));
|
execute(device -> device.setAttenuationStep(attenuation.getValue()));
|
||||||
mAttenuation = attenuation;
|
mAttenuation = attenuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the device firmware supports a bias-T, available once the tuner is started.
|
|
||||||
* @return true if supported
|
|
||||||
*/
|
|
||||||
public boolean isBiasTSupported()
|
|
||||||
{
|
|
||||||
return mBiasTSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the Bias-T power output is enabled.
|
|
||||||
* @return enabled state
|
|
||||||
*/
|
|
||||||
public boolean getBiasT()
|
|
||||||
{
|
|
||||||
return mBiasTEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables/disables the Bias-T (RF bias tee) power output for active antennas
|
|
||||||
* @param enabled true to enable Bias-T, false to disable
|
|
||||||
* @throws SourceException if the device doesn't support a bias-T or there is an error
|
|
||||||
*/
|
|
||||||
public void setBiasT(boolean enabled) throws SourceException
|
|
||||||
{
|
|
||||||
if(!mBiasTSupported)
|
|
||||||
{
|
|
||||||
throw new SourceException("Airspy HF firmware doesn't support bias-T");
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(device -> device.setBiasTee(enabled ? 1 : 0));
|
|
||||||
mBiasTEnabled = enabled;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ public class AirspyHfTunerEditor extends TunerEditor<AirspyHfTuner,AirspyHfTuner
|
|||||||
private JComboBox<Attenuation> mAttenuationCombo;
|
private JComboBox<Attenuation> mAttenuationCombo;
|
||||||
private JToggleButton mAgcToggleButton;
|
private JToggleButton mAgcToggleButton;
|
||||||
private JToggleButton mLnaToggleButton;
|
private JToggleButton mLnaToggleButton;
|
||||||
private JToggleButton mBiasTToggleButton;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -101,7 +100,6 @@ public class AirspyHfTunerEditor extends TunerEditor<AirspyHfTuner,AirspyHfTuner
|
|||||||
buttonPanel.setLayout(new MigLayout("insets 0", "[left]", ""));
|
buttonPanel.setLayout(new MigLayout("insets 0", "[left]", ""));
|
||||||
buttonPanel.add(getAgcToggleButton());
|
buttonPanel.add(getAgcToggleButton());
|
||||||
buttonPanel.add(getLnaToggleButton());
|
buttonPanel.add(getLnaToggleButton());
|
||||||
buttonPanel.add(getBiasTToggleButton());
|
|
||||||
add(new JLabel(" "));
|
add(new JLabel(" "));
|
||||||
add(buttonPanel, "wrap");
|
add(buttonPanel, "wrap");
|
||||||
|
|
||||||
@@ -127,12 +125,6 @@ public class AirspyHfTunerEditor extends TunerEditor<AirspyHfTuner,AirspyHfTuner
|
|||||||
getConfiguration().setAgc(getAgcToggleButton().isSelected());
|
getConfiguration().setAgc(getAgcToggleButton().isSelected());
|
||||||
getConfiguration().setLna(getLnaToggleButton().isSelected());
|
getConfiguration().setLna(getLnaToggleButton().isSelected());
|
||||||
|
|
||||||
//Only update bias-T when the device supports it, so the saved setting isn't lost on unsupported firmware.
|
|
||||||
if(getTuner().getController().isBiasTSupported())
|
|
||||||
{
|
|
||||||
getConfiguration().setBiasT(getBiasTToggleButton().isSelected());
|
|
||||||
}
|
|
||||||
|
|
||||||
saveConfiguration();
|
saveConfiguration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,11 +158,6 @@ public class AirspyHfTunerEditor extends TunerEditor<AirspyHfTuner,AirspyHfTuner
|
|||||||
getAgcToggleButton().setSelected(getTuner().getController().getAgc());
|
getAgcToggleButton().setSelected(getTuner().getController().getAgc());
|
||||||
getLnaToggleButton().setEnabled(true);
|
getLnaToggleButton().setEnabled(true);
|
||||||
getLnaToggleButton().setSelected(getTuner().getController().getLna());
|
getLnaToggleButton().setSelected(getTuner().getController().getLna());
|
||||||
boolean biasTSupported = getTuner().getController().isBiasTSupported();
|
|
||||||
getBiasTToggleButton().setEnabled(biasTSupported);
|
|
||||||
getBiasTToggleButton().setSelected(biasTSupported && getTuner().getController().getBiasT());
|
|
||||||
getBiasTToggleButton().setToolTipText(biasTSupported ? "Enable Bias-T power output for active antennas" :
|
|
||||||
"Bias-T not supported by this device or firmware");
|
|
||||||
getSampleRateCombo().setSelectedItem(getTuner().getController().getCurrentAirspySampleRate());
|
getSampleRateCombo().setSelectedItem(getTuner().getController().getCurrentAirspySampleRate());
|
||||||
getAttenuationCombo().setEnabled(!getTuner().getController().getAgc());
|
getAttenuationCombo().setEnabled(!getTuner().getController().getAgc());
|
||||||
getAttenuationCombo().setSelectedItem(getTuner().getController().getAttenuation());
|
getAttenuationCombo().setSelectedItem(getTuner().getController().getAttenuation());
|
||||||
@@ -182,9 +169,6 @@ public class AirspyHfTunerEditor extends TunerEditor<AirspyHfTuner,AirspyHfTuner
|
|||||||
getAgcToggleButton().setSelected(false);
|
getAgcToggleButton().setSelected(false);
|
||||||
getLnaToggleButton().setEnabled(false);
|
getLnaToggleButton().setEnabled(false);
|
||||||
getLnaToggleButton().setSelected(false);
|
getLnaToggleButton().setSelected(false);
|
||||||
getBiasTToggleButton().setEnabled(false);
|
|
||||||
getBiasTToggleButton().setSelected(false);
|
|
||||||
getBiasTToggleButton().setToolTipText("No tuner available");
|
|
||||||
getSampleRateCombo().setEnabled(false);
|
getSampleRateCombo().setEnabled(false);
|
||||||
getAttenuationCombo().setEnabled(false);
|
getAttenuationCombo().setEnabled(false);
|
||||||
getAttenuationCombo().setSelectedItem(Attenuation.A0);
|
getAttenuationCombo().setSelectedItem(Attenuation.A0);
|
||||||
@@ -360,36 +344,4 @@ public class AirspyHfTunerEditor extends TunerEditor<AirspyHfTuner,AirspyHfTuner
|
|||||||
|
|
||||||
return mLnaToggleButton;
|
return mLnaToggleButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Bias-T toggle button, enabled only when the device firmware supports a bias-T.
|
|
||||||
*/
|
|
||||||
private JToggleButton getBiasTToggleButton()
|
|
||||||
{
|
|
||||||
if(mBiasTToggleButton == null)
|
|
||||||
{
|
|
||||||
mBiasTToggleButton = new JToggleButton("Bias-T");
|
|
||||||
mBiasTToggleButton.setToolTipText("Enable Bias-T power output for active antennas");
|
|
||||||
mBiasTToggleButton.setEnabled(false);
|
|
||||||
mBiasTToggleButton.addActionListener(e -> {
|
|
||||||
if(hasTuner() && !isLoading())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getTuner().getController().setBiasT(mBiasTToggleButton.isSelected());
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
catch(SourceException se)
|
|
||||||
{
|
|
||||||
mLog.error("Error setting Airspy HF Bias-T", se);
|
|
||||||
mBiasTToggleButton.setSelected(getTuner().getController().getBiasT());
|
|
||||||
JOptionPane.showMessageDialog(AirspyHfTunerEditor.this,
|
|
||||||
"Airspy HF Tuner Controller - couldn't change Bias-T setting " + se.getLocalizedMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mBiasTToggleButton;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,13 +45,17 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
|||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.UsbDeviceLookup;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.usb4java.Context;
|
||||||
|
import org.usb4java.Device;
|
||||||
|
import org.usb4java.DeviceList;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bladeRF tuner controller, implemented with the jBladeRF library (libbladeRF).
|
* bladeRF tuner controller, implemented with the jBladeRF library (libbladeRF).
|
||||||
@@ -372,7 +376,46 @@ public class BladeRfTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
private int readUsbAddress() throws SourceException
|
private int readUsbAddress() throws SourceException
|
||||||
{
|
{
|
||||||
return UsbDeviceLookup.find(mBus, mPortAddress).address();
|
Context context = new Context();
|
||||||
|
int status = LibUsb.init(context);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DeviceList deviceList = new DeviceList();
|
||||||
|
int count = LibUsb.getDeviceList(context, deviceList);
|
||||||
|
|
||||||
|
if(count < 0)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(Device device : deviceList)
|
||||||
|
{
|
||||||
|
if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 &&
|
||||||
|
mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device)))
|
||||||
|
{
|
||||||
|
return LibUsb.getDeviceAddress(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.freeDeviceList(deviceList, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.exit(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerConfiguration;
|
|||||||
import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
|
import io.github.dsheirer.source.tuner.fcd.proplusV2.FCD2TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
|
import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration;
|
import io.github.dsheirer.source.tuner.hydrasdr.HydraSdrTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.limesdr.LimeSdrTunerConfiguration;
|
|
||||||
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
|
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.rtl.fc0012.FC0012TunerConfiguration;
|
import io.github.dsheirer.source.tuner.rtl.fc0012.FC0012TunerConfiguration;
|
||||||
@@ -57,7 +56,6 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
|
|||||||
@JsonSubTypes.Type(value = FCD2TunerConfiguration.class, name = "fcd2TunerConfiguration"),
|
@JsonSubTypes.Type(value = FCD2TunerConfiguration.class, name = "fcd2TunerConfiguration"),
|
||||||
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
|
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
|
||||||
@JsonSubTypes.Type(value = HydraSdrTunerConfiguration.class, name = "hydraSdrTunerConfiguration"),
|
@JsonSubTypes.Type(value = HydraSdrTunerConfiguration.class, name = "hydraSdrTunerConfiguration"),
|
||||||
@JsonSubTypes.Type(value = LimeSdrTunerConfiguration.class, name = "limeSdrTunerConfiguration"),
|
|
||||||
@JsonSubTypes.Type(value = RecordingTunerConfiguration.class, name = "recordingTunerConfiguration"),
|
@JsonSubTypes.Type(value = RecordingTunerConfiguration.class, name = "recordingTunerConfiguration"),
|
||||||
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
|
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
|
||||||
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
|
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ package io.github.dsheirer.source.tuner.fcd;
|
|||||||
public enum FCDCommand
|
public enum FCDCommand
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Note: the libusb interrupt transfers used here (through jlibusb) are slightly different than
|
* Note: the javaxUSB and usb4Java libraries are slightly different than
|
||||||
* the signal9 HID libraries used in C, so you will see variations in the
|
* the signal9 HID libraries used in C, so you will see variations in the
|
||||||
* byte array length and indexes, when comparing this setup to the qthid
|
* byte array length and indexes, when comparing this setup to the qthid
|
||||||
* source code.
|
* source code.
|
||||||
|
|||||||
@@ -19,11 +19,6 @@
|
|||||||
package io.github.dsheirer.source.tuner.fcd;
|
package io.github.dsheirer.source.tuner.fcd;
|
||||||
|
|
||||||
import io.github.dsheirer.buffer.INativeBuffer;
|
import io.github.dsheirer.buffer.INativeBuffer;
|
||||||
import io.github.dsheirer.libusb.LibUsb;
|
|
||||||
import io.github.dsheirer.libusb.UsbContext;
|
|
||||||
import io.github.dsheirer.libusb.UsbDeviceHandle;
|
|
||||||
import io.github.dsheirer.libusb.UsbDeviceInfo;
|
|
||||||
import io.github.dsheirer.libusb.UsbException;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.adapter.ComplexShortAdapter;
|
import io.github.dsheirer.sample.adapter.ComplexShortAdapter;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
@@ -33,13 +28,21 @@ import io.github.dsheirer.source.tuner.MixerTunerType;
|
|||||||
import io.github.dsheirer.source.tuner.TunerClass;
|
import io.github.dsheirer.source.tuner.TunerClass;
|
||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
import io.github.dsheirer.source.tuner.manager.UsbDeviceLookup;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.usb4java.Context;
|
||||||
|
import org.usb4java.Device;
|
||||||
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
import org.usb4java.DeviceHandle;
|
||||||
|
import org.usb4java.DeviceList;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
import org.usb4java.LibUsbException;
|
||||||
|
|
||||||
import javax.sound.sampled.TargetDataLine;
|
import javax.sound.sampled.TargetDataLine;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.IntBuffer;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
public abstract class FCDTunerController extends TunerController
|
public abstract class FCDTunerController extends TunerController
|
||||||
@@ -47,19 +50,16 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(FCDTunerController.class);
|
private final static Logger mLog = LoggerFactory.getLogger(FCDTunerController.class);
|
||||||
private static final double USABLE_BANDWIDTH_PERCENT = 1.0;
|
private static final double USABLE_BANDWIDTH_PERCENT = 1.0;
|
||||||
private static final int DC_SPIKE_AVOID_HALF_BANDWIDTH = 5000;
|
private static final int DC_SPIKE_AVOID_HALF_BANDWIDTH = 5000;
|
||||||
private final static int FCD_INTERFACE = 0x2;
|
private final static byte FCD_INTERFACE = (byte) 0x2;
|
||||||
private final static int FCD_ENDPOINT_IN = 0x82;
|
private final static byte FCD_ENDPOINT_IN = (byte) 0x82;
|
||||||
private final static int FCD_ENDPOINT_OUT = 0x2;
|
private final static byte FCD_ENDPOINT_OUT = (byte) 0x2;
|
||||||
private final static int FCD_PACKET_SIZE = 64;
|
|
||||||
private final static int FCD_TRANSFER_TIMEOUT_MS = 500;
|
|
||||||
private final static int LIBUSB_ERROR_NO_DEVICE = -4;
|
|
||||||
|
|
||||||
private int mBus;
|
private int mBus;
|
||||||
private String mPortAddress;
|
private String mPortAddress;
|
||||||
private UsbContext mUsbContext;
|
private Context mDeviceContext = new Context();
|
||||||
private UsbDeviceInfo mDevice;
|
private Device mDevice;
|
||||||
private volatile UsbDeviceHandle mDeviceHandle;
|
private DeviceDescriptor mDeviceDescriptor = new DeviceDescriptor();
|
||||||
private boolean mShutdown = false;
|
private DeviceHandle mDeviceHandle = new DeviceHandle();
|
||||||
|
|
||||||
private FCDConfiguration mConfiguration = new FCDConfiguration();
|
private FCDConfiguration mConfiguration = new FCDConfiguration();
|
||||||
protected ComplexMixer mComplexMixer;
|
protected ComplexMixer mComplexMixer;
|
||||||
@@ -168,6 +168,58 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the USB device for this tuner at the specified USB bus and port.
|
||||||
|
* @return discovered USB device
|
||||||
|
* @throws SourceException if there is an error or the device is not discovered.
|
||||||
|
*/
|
||||||
|
private Device findDevice() throws SourceException
|
||||||
|
{
|
||||||
|
Device foundDevice = null;
|
||||||
|
|
||||||
|
DeviceList deviceList = new DeviceList();
|
||||||
|
int count = LibUsb.getDeviceList(mDeviceContext, deviceList);
|
||||||
|
|
||||||
|
if(count >= 0)
|
||||||
|
{
|
||||||
|
for(Device device: deviceList)
|
||||||
|
{
|
||||||
|
int bus = LibUsb.getBusNumber(device);
|
||||||
|
int port = LibUsb.getPortNumber(device);
|
||||||
|
|
||||||
|
if(port > 0)
|
||||||
|
{
|
||||||
|
String portAddress = TunerManager.getPortAddress(device);
|
||||||
|
|
||||||
|
if(mBus == bus && mPortAddress != null && mPortAddress.equals(portAddress))
|
||||||
|
{
|
||||||
|
foundDevice = device;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LibUsb.unrefDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LibUsb.unrefDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new SourceException("LibUsb couldn't discover USB device [" + mBus + ":" + mPortAddress +
|
||||||
|
"] from device list" + (count < 0 ? " - error: " + LibUsb.errorName(count) : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(foundDevice == null)
|
||||||
|
{
|
||||||
|
throw new SourceException("LibUsb couldn't find the matching USB device");
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundDevice;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the controller by opening the USB device and claiming the HID interface.
|
* Initializes the controller by opening the USB device and claiming the HID interface.
|
||||||
* Invoke this method after constructing this class to setup the controller.
|
* Invoke this method after constructing this class to setup the controller.
|
||||||
@@ -176,102 +228,80 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void start() throws SourceException
|
public void start() throws SourceException
|
||||||
{
|
{
|
||||||
if(mShutdown)
|
if(mDeviceContext == null)
|
||||||
{
|
{
|
||||||
throw new SourceException("Device cannot be reused once it has been shutdown");
|
throw new SourceException("Device cannot be reused once it has been shutdown");
|
||||||
}
|
}
|
||||||
|
|
||||||
mUsbContext = UsbDeviceLookup.createContext();
|
int status = LibUsb.init(mDeviceContext);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
mDevice = findDevice();
|
||||||
|
mDeviceDescriptor = new DeviceDescriptor();
|
||||||
|
status = LibUsb.getDeviceDescriptor(mDevice, mDeviceDescriptor);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mDeviceDescriptor = null;
|
||||||
|
throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
mDeviceHandle = new DeviceHandle();
|
||||||
|
status = LibUsb.open(mDevice, mDeviceHandle);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mDeviceHandle = null;
|
||||||
|
mDeviceDescriptor = null;
|
||||||
|
|
||||||
|
mLog.error("Can't open USB tuner [" + getTunerType() + "] - check driver or Linux udev rules");
|
||||||
|
throw new SourceException("Can't open USB tuner - check driver or Linux udev rules - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove a kernel driver if active
|
||||||
|
status = LibUsb.kernelDriverActive(mDeviceHandle, FCD_INTERFACE);
|
||||||
|
|
||||||
|
if(status == 1)
|
||||||
|
{
|
||||||
|
status = LibUsb.detachKernelDriver(mDeviceHandle, FCD_INTERFACE);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Unable to detach kernel driver for USB tuner device - bus:" + mBus + " port:" + mPortAddress);
|
||||||
|
mDeviceHandle = null;
|
||||||
|
mDeviceDescriptor = null;
|
||||||
|
throw new SourceException("Can't detach kernel driver");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Claim the interface
|
||||||
|
status = LibUsb.claimInterface(mDeviceHandle, FCD_INTERFACE);
|
||||||
|
|
||||||
|
if(status == LibUsb.ERROR_BUSY)
|
||||||
|
{
|
||||||
|
mDeviceHandle = null;
|
||||||
|
mDeviceDescriptor = null;
|
||||||
|
throw new SourceException("USB tuner is in-use by another application");
|
||||||
|
}
|
||||||
|
else if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mDeviceHandle = null;
|
||||||
|
mDeviceDescriptor = null;
|
||||||
|
throw new SourceException("Can't claim interface on USB tuner - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mDevice = UsbDeviceLookup.find(mUsbContext, mBus, mPortAddress);
|
deviceStart();
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mDeviceHandle = mUsbContext.open(mDevice);
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
mLog.error("Can't open USB tuner [" + getTunerType() + "] - check driver or Linux udev rules");
|
|
||||||
throw new SourceException("Can't open USB tuner - check driver or Linux udev rules - " +
|
|
||||||
LibUsb.errorName(e.code()));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Remove a kernel driver if active
|
|
||||||
boolean kernelDriverActive;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
kernelDriverActive = mDeviceHandle.isKernelDriverActive(FCD_INTERFACE);
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
kernelDriverActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(kernelDriverActive)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mDeviceHandle.detachKernelDriver(FCD_INTERFACE);
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
mLog.error("Unable to detach kernel driver for USB tuner device - bus:" + mBus + " port:" + mPortAddress);
|
|
||||||
throw new SourceException("Can't detach kernel driver");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Claim the interface
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mDeviceHandle.claimInterface(FCD_INTERFACE);
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
if(e.code() == UsbDeviceLookup.ERROR_BUSY)
|
|
||||||
{
|
|
||||||
throw new SourceException("USB tuner is in-use by another application");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SourceException("Can't claim interface on USB tuner - " + LibUsb.errorName(e.code()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch(SourceException se)
|
catch(SourceException se)
|
||||||
{
|
{
|
||||||
closeUsb();
|
|
||||||
throw se;
|
throw se;
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the USB device handle and the libusb context.
|
|
||||||
*/
|
|
||||||
private void closeUsb()
|
|
||||||
{
|
|
||||||
UsbDeviceHandle handle = mDeviceHandle;
|
|
||||||
mDeviceHandle = null;
|
|
||||||
|
|
||||||
if(handle != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
handle.close();
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
mLog.error("error while closing device handle", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mUsbContext != null)
|
|
||||||
{
|
|
||||||
mUsbContext.close();
|
|
||||||
mUsbContext = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -291,9 +321,23 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
mComplexMixer = null;
|
mComplexMixer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
closeUsb();
|
if(mDeviceHandle != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LibUsb.close(mDeviceHandle);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("error while closing device handle", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mDeviceDescriptor = null;
|
||||||
|
mDeviceHandle = null;
|
||||||
mDevice = null;
|
mDevice = null;
|
||||||
mShutdown = true;
|
LibUsb.exit(mDeviceContext);
|
||||||
|
mDeviceContext = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -316,14 +360,19 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public String getUSBAddress()
|
public String getUSBAddress()
|
||||||
{
|
{
|
||||||
UsbDeviceInfo device = mDevice;
|
if(mDevice != null)
|
||||||
|
|
||||||
//This is the tuner's unique ID for saved tuner configurations, so the format must not change: the port is the
|
|
||||||
//last port number in the path from the root hub, as libusb_get_port_number() reports it.
|
|
||||||
if(device != null)
|
|
||||||
{
|
{
|
||||||
int port = device.portNumbers().isEmpty() ? 0 : device.portNumbers().getLast();
|
StringBuilder sb = new StringBuilder();
|
||||||
return "Bus:" + device.busNumber() + " Port:" + port;
|
|
||||||
|
sb.append("Bus:");
|
||||||
|
int bus = LibUsb.getBusNumber(mDevice);
|
||||||
|
sb.append(bus);
|
||||||
|
|
||||||
|
sb.append(" Port:");
|
||||||
|
int port = LibUsb.getPortNumber(mDevice);
|
||||||
|
sb.append(port);
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
@@ -334,34 +383,38 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public String getUSBID()
|
public String getUSBID()
|
||||||
{
|
{
|
||||||
UsbDeviceInfo device = mDevice;
|
if(mDeviceDescriptor != null)
|
||||||
|
|
||||||
if(device != null)
|
|
||||||
{
|
{
|
||||||
return String.format("%04X:%04X", device.vendorId(), device.productId());
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append(String.format("%04X", (int) (mDeviceDescriptor.idVendor() & 0xFFFF)));
|
||||||
|
sb.append(":");
|
||||||
|
sb.append(String.format("%04X", (int) (mDeviceDescriptor.idProduct() & 0xFFFF)));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* USB Port Speed. The FUNcube Dongle Pro is a full speed (USB 1.1) device.
|
* USB Port Speed. Should be 2.0 for both types of funcube dongles
|
||||||
*/
|
*/
|
||||||
public String getUSBSpeed()
|
public String getUSBSpeed()
|
||||||
{
|
{
|
||||||
UsbDeviceInfo device = mDevice;
|
if(mDevice != null)
|
||||||
|
|
||||||
if(device != null)
|
|
||||||
{
|
{
|
||||||
switch(device.speed())
|
int speed = LibUsb.getDeviceSpeed(mDevice);
|
||||||
|
|
||||||
|
switch(speed)
|
||||||
{
|
{
|
||||||
case LOW:
|
case 0:
|
||||||
return "1.1 LOW";
|
return "1.1 LOW";
|
||||||
case FULL:
|
case 1:
|
||||||
return "1.1 FULL";
|
return "1.1 FULL";
|
||||||
case HIGH:
|
case 2:
|
||||||
return "2.0 HIGH";
|
return "2.0 HIGH";
|
||||||
case SUPER:
|
case 3:
|
||||||
return "3.0 SUPER";
|
return "3.0 SUPER";
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -447,27 +500,26 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs an interrupt write of one 64 byte packet to the OUT endpoint.
|
* Performs an interrupt write to the OUT endpoint.
|
||||||
*
|
*
|
||||||
* @param packet to write. Must be 64 bytes in length.
|
* @param buffer - direct allocated buffer. Must be 64 bytes in length.
|
||||||
* @throws FCDUsbException on error
|
* @throws LibUsbException on error
|
||||||
*/
|
*/
|
||||||
private void write(byte[] packet) throws FCDUsbException
|
private void write(ByteBuffer buffer) throws LibUsbException
|
||||||
{
|
{
|
||||||
UsbDeviceHandle handle = mDeviceHandle;
|
if(mDeviceHandle != null)
|
||||||
|
{
|
||||||
|
IntBuffer transferred = IntBuffer.allocate(1);
|
||||||
|
int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_OUT, buffer, transferred, 500l);
|
||||||
|
|
||||||
if(handle == null)
|
if(result != LibUsb.SUCCESS)
|
||||||
{
|
{
|
||||||
throw new FCDUsbException("device handle is null", LIBUSB_ERROR_NO_DEVICE);
|
throw new LibUsbException("error writing byte buffer", result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
try
|
|
||||||
{
|
{
|
||||||
handle.interruptTransfer(FCD_ENDPOINT_OUT, packet, packet.length, FCD_TRANSFER_TIMEOUT_MS);
|
throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE);
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
throw new FCDUsbException("error writing byte buffer", e.code());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,14 +527,14 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
* Performs an interrupt write to the OUT endpoint for the FCD command.
|
* Performs an interrupt write to the OUT endpoint for the FCD command.
|
||||||
*
|
*
|
||||||
* @param command - no-argument command to write
|
* @param command - no-argument command to write
|
||||||
* @throws FCDUsbException - on error
|
* @throws LibUsbException - on error
|
||||||
*/
|
*/
|
||||||
private void write(FCDCommand command) throws FCDUsbException
|
private void write(FCDCommand command) throws LibUsbException
|
||||||
{
|
{
|
||||||
byte[] packet = new byte[FCD_PACKET_SIZE];
|
ByteBuffer buffer = ByteBuffer.allocateDirect(64);
|
||||||
packet[0] = command.getCommand();
|
buffer.put(0, command.getCommand());
|
||||||
packet[1] = (byte) 0x00;
|
buffer.put(1, (byte) 0x00);
|
||||||
write(packet);
|
write(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -490,53 +542,46 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
*
|
*
|
||||||
* @param command - command to write
|
* @param command - command to write
|
||||||
* @param argument - value to write with the command
|
* @param argument - value to write with the command
|
||||||
* @throws FCDUsbException - on error
|
* @throws LibUsbException - on error
|
||||||
*/
|
*/
|
||||||
private void write(FCDCommand command, long argument) throws FCDUsbException
|
private void write(FCDCommand command, long argument) throws LibUsbException
|
||||||
{
|
{
|
||||||
byte[] packet = new byte[FCD_PACKET_SIZE];
|
ByteBuffer buffer = ByteBuffer.allocateDirect(64);
|
||||||
ByteBuffer buffer = ByteBuffer.wrap(packet);
|
|
||||||
/* The FCD expects little-endian formatted values */
|
/* The FCD expects little-endian formatted values */
|
||||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
buffer.put(0, command.getCommand());
|
buffer.put(0, command.getCommand());
|
||||||
buffer.putLong(1, argument);
|
buffer.putLong(1, argument);
|
||||||
write(packet);
|
write(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs an interrupt read of one 64 byte packet from the IN endpoint
|
* Performs an interrupt read against the endpoint
|
||||||
*
|
*
|
||||||
* @return buffer read from FCD
|
* @return buffer read from FCD
|
||||||
* @throws FCDUsbException on error
|
* @throws LibUsbException on error
|
||||||
*/
|
*/
|
||||||
private ByteBuffer read() throws FCDUsbException
|
private ByteBuffer read() throws LibUsbException
|
||||||
{
|
{
|
||||||
UsbDeviceHandle handle = mDeviceHandle;
|
if(mDeviceHandle != null)
|
||||||
|
|
||||||
if(handle == null)
|
|
||||||
{
|
{
|
||||||
throw new FCDUsbException("device handle is null", LIBUSB_ERROR_NO_DEVICE);
|
ByteBuffer buffer = ByteBuffer.allocateDirect(64);
|
||||||
|
IntBuffer transferred = IntBuffer.allocate(1);
|
||||||
|
int result = LibUsb.interruptTransfer(mDeviceHandle, FCD_ENDPOINT_IN, buffer, transferred, 500l);
|
||||||
|
|
||||||
|
if(result != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new LibUsbException("error reading byte buffer", result);
|
||||||
|
}
|
||||||
|
else if(transferred.get(0) != buffer.capacity())
|
||||||
|
{
|
||||||
|
throw new LibUsbException("received bytes [" + transferred.get(0) + "] didn't match expected length [" +
|
||||||
|
buffer.capacity() + "]", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] packet = new byte[FCD_PACKET_SIZE];
|
throw new LibUsbException("device handle is null", LibUsb.ERROR_NO_DEVICE);
|
||||||
int transferred;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
transferred = handle.interruptTransfer(FCD_ENDPOINT_IN, packet, packet.length, FCD_TRANSFER_TIMEOUT_MS);
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
throw new FCDUsbException("error reading byte buffer", e.code());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(transferred != packet.length)
|
|
||||||
{
|
|
||||||
throw new FCDUsbException("received bytes [" + transferred + "] didn't match expected length [" +
|
|
||||||
packet.length + "]", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ByteBuffer.wrap(packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -545,9 +590,9 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
*
|
*
|
||||||
* @param command - command to send
|
* @param command - command to send
|
||||||
* @param argument - command argument to send
|
* @param argument - command argument to send
|
||||||
* @throws FCDUsbException - on error
|
* @throws LibUsbException - on error
|
||||||
*/
|
*/
|
||||||
protected synchronized void send(FCDCommand command, long argument) throws FCDUsbException
|
protected synchronized void send(FCDCommand command, long argument) throws LibUsbException
|
||||||
{
|
{
|
||||||
write(command, argument);
|
write(command, argument);
|
||||||
read();
|
read();
|
||||||
@@ -558,9 +603,9 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
* command.
|
* command.
|
||||||
*
|
*
|
||||||
* @param command - command to send
|
* @param command - command to send
|
||||||
* @throws FCDUsbException - on error
|
* @throws LibUsbException - on error
|
||||||
*/
|
*/
|
||||||
protected ByteBuffer send(FCDCommand command) throws FCDUsbException
|
protected ByteBuffer send(FCDCommand command) throws LibUsbException
|
||||||
{
|
{
|
||||||
write(command);
|
write(command);
|
||||||
return read();
|
return read();
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.fcd;
|
|
||||||
|
|
||||||
import io.github.dsheirer.libusb.LibUsb;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* USB transfer error while communicating with a FUNcube Dongle. Unchecked, like the usb4java exception it replaces,
|
|
||||||
* since the FCD command methods are called from many places that handle any exception.
|
|
||||||
*/
|
|
||||||
public class FCDUsbException extends RuntimeException
|
|
||||||
{
|
|
||||||
private final int mErrorCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
* @param message describing the failed operation
|
|
||||||
* @param errorCode libusb error code, or 0 when the transfer succeeded but returned unexpected data
|
|
||||||
*/
|
|
||||||
public FCDUsbException(String message, int errorCode)
|
|
||||||
{
|
|
||||||
super(errorCode < 0 ? message + " - " + LibUsb.errorName(errorCode) : message);
|
|
||||||
mErrorCode = errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* libusb error code, such as -7 for LIBUSB_ERROR_TIMEOUT, or 0
|
|
||||||
*/
|
|
||||||
public int getErrorCode()
|
|
||||||
{
|
|
||||||
return mErrorCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,6 @@ public class FCD1TunerConfiguration extends TunerConfiguration
|
|||||||
private LNAGain mLNAGain = LNAGain.LNA_GAIN_PLUS_20_0;
|
private LNAGain mLNAGain = LNAGain.LNA_GAIN_PLUS_20_0;
|
||||||
private LNAEnhance mLNAEnhance = LNAEnhance.LNA_ENHANCE_OFF;
|
private LNAEnhance mLNAEnhance = LNAEnhance.LNA_ENHANCE_OFF;
|
||||||
private MixerGain mMixerGain = MixerGain.MIXER_GAIN_PLUS_12_0;
|
private MixerGain mMixerGain = MixerGain.MIXER_GAIN_PLUS_12_0;
|
||||||
private boolean mBiasT = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor for JAXB
|
* Default constructor for JAXB
|
||||||
@@ -133,15 +132,4 @@ public class FCD1TunerConfiguration extends TunerConfiguration
|
|||||||
{
|
{
|
||||||
mGainCorrection = value;
|
mGainCorrection = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "bias_t")
|
|
||||||
public boolean isBiasT()
|
|
||||||
{
|
|
||||||
return mBiasT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBiasT(boolean enabled)
|
|
||||||
{
|
|
||||||
mBiasT = enabled;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ public class FCD1TunerController extends FCDTunerController
|
|||||||
private LNAGain mLNAGain;
|
private LNAGain mLNAGain;
|
||||||
private LNAEnhance mLNAEnhance;
|
private LNAEnhance mLNAEnhance;
|
||||||
private MixerGain mMixerGain;
|
private MixerGain mMixerGain;
|
||||||
private boolean mBiasTEnabled = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -124,61 +123,9 @@ public class FCD1TunerController extends FCDTunerController
|
|||||||
//Re-cast any usb exceptions as source exceptions
|
//Re-cast any usb exceptions as source exceptions
|
||||||
throw new SourceException("FCD 1 Controller error applying tuner config - " + e.getLocalizedMessage());
|
throw new SourceException("FCD 1 Controller error applying tuner config - " + e.getLocalizedMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
//Apply the bias-T separately so that a bias-T error doesn't prevent the tuner from starting.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setBiasT(fcd1.isBiasT());
|
|
||||||
}
|
|
||||||
catch(SourceException se)
|
|
||||||
{
|
|
||||||
mLog.warn("FCD 1 Controller couldn't apply bias-T setting - " + se.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables/disables the Bias-T (RF bias tee) power output for active antennas
|
|
||||||
*
|
|
||||||
* @param enabled true to enable Bias-T, false to disable
|
|
||||||
* @throws SourceException on error
|
|
||||||
*/
|
|
||||||
public void setBiasT(boolean enabled) throws SourceException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
send(FCDCommand.APP_SET_BIAS_TEE, enabled ? 1 : 0);
|
|
||||||
mBiasTEnabled = enabled;
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
throw new SourceException("Couldn't set FCD Pro bias-T", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns off the bias-T, when enabled, before closing the device.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void stop()
|
|
||||||
{
|
|
||||||
if(mBiasTEnabled)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setBiasT(false);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
mLog.warn("Error turning off FCD Pro bias-T before closing the device - " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
mBiasTEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current LNA Gain value from the controller and stores it
|
* Gets the current LNA Gain value from the controller and stores it
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
package io.github.dsheirer.source.tuner.fcd.proV1;
|
package io.github.dsheirer.source.tuner.fcd.proV1;
|
||||||
|
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.source.SourceException;
|
|
||||||
import io.github.dsheirer.source.tuner.fcd.FCDTuner;
|
import io.github.dsheirer.source.tuner.fcd.FCDTuner;
|
||||||
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController.LNAEnhance;
|
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController.LNAEnhance;
|
||||||
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController.LNAGain;
|
import io.github.dsheirer.source.tuner.fcd.proV1.FCD1TunerController.LNAGain;
|
||||||
@@ -33,7 +32,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JCheckBox;
|
|
||||||
import javax.swing.JComboBox;
|
import javax.swing.JComboBox;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
@@ -54,7 +52,6 @@ public class FCD1TunerEditor extends TunerEditor<FCDTuner,FCD1TunerConfiguration
|
|||||||
private JComboBox<LNAGain> mLnaGainCombo;
|
private JComboBox<LNAGain> mLnaGainCombo;
|
||||||
private JComboBox<LNAEnhance> mLnaEnhanceCombo;
|
private JComboBox<LNAEnhance> mLnaEnhanceCombo;
|
||||||
private JComboBox<MixerGain> mMixerGainCombo;
|
private JComboBox<MixerGain> mMixerGainCombo;
|
||||||
private JCheckBox mBiasTCheckBox;
|
|
||||||
private CorrectionSpinner mCorrectionDCI;
|
private CorrectionSpinner mCorrectionDCI;
|
||||||
private CorrectionSpinner mCorrectionDCQ;
|
private CorrectionSpinner mCorrectionDCQ;
|
||||||
private CorrectionSpinner mGainCorrectionSpinner;
|
private CorrectionSpinner mGainCorrectionSpinner;
|
||||||
@@ -124,7 +121,6 @@ public class FCD1TunerEditor extends TunerEditor<FCDTuner,FCD1TunerConfiguration
|
|||||||
getMixerGainCombo().setSelectedItem(getConfiguration().getMixerGain());
|
getMixerGainCombo().setSelectedItem(getConfiguration().getMixerGain());
|
||||||
getLnaEnhanceCombo().setSelectedItem(getConfiguration().getLNAEnhance());
|
getLnaEnhanceCombo().setSelectedItem(getConfiguration().getLNAEnhance());
|
||||||
getLnaGainCombo().setSelectedItem(getConfiguration().getLNAGain());
|
getLnaGainCombo().setSelectedItem(getConfiguration().getLNAGain());
|
||||||
getBiasTCheckBox().setSelected(getConfiguration().isBiasT());
|
|
||||||
getDcCorrectionSpinnerI().setValue(getConfiguration().getInphaseDCCorrection());
|
getDcCorrectionSpinnerI().setValue(getConfiguration().getInphaseDCCorrection());
|
||||||
getDcCorrectionSpinnerQ().setValue(getConfiguration().getQuadratureDCCorrection());
|
getDcCorrectionSpinnerQ().setValue(getConfiguration().getQuadratureDCCorrection());
|
||||||
getPhaseCorrectionSpinner().setValue(getConfiguration().getGainCorrection());
|
getPhaseCorrectionSpinner().setValue(getConfiguration().getGainCorrection());
|
||||||
@@ -134,7 +130,6 @@ public class FCD1TunerEditor extends TunerEditor<FCDTuner,FCD1TunerConfiguration
|
|||||||
getMixerGainCombo().setEnabled(hasTuner());
|
getMixerGainCombo().setEnabled(hasTuner());
|
||||||
getLnaEnhanceCombo().setEnabled(hasTuner());
|
getLnaEnhanceCombo().setEnabled(hasTuner());
|
||||||
getLnaGainCombo().setEnabled(hasTuner());
|
getLnaGainCombo().setEnabled(hasTuner());
|
||||||
getBiasTCheckBox().setEnabled(hasTuner());
|
|
||||||
getDcCorrectionSpinnerI().setEnabled(hasTuner());
|
getDcCorrectionSpinnerI().setEnabled(hasTuner());
|
||||||
getDcCorrectionSpinnerQ().setEnabled(hasTuner());
|
getDcCorrectionSpinnerQ().setEnabled(hasTuner());
|
||||||
getPhaseCorrectionSpinner().setEnabled(hasTuner());
|
getPhaseCorrectionSpinner().setEnabled(hasTuner());
|
||||||
@@ -169,8 +164,6 @@ public class FCD1TunerEditor extends TunerEditor<FCDTuner,FCD1TunerConfiguration
|
|||||||
add(getLnaEnhanceCombo(), "wrap");
|
add(getLnaEnhanceCombo(), "wrap");
|
||||||
add(new JLabel("Mixer:"));
|
add(new JLabel("Mixer:"));
|
||||||
add(getMixerGainCombo(), "wrap");
|
add(getMixerGainCombo(), "wrap");
|
||||||
add(new JLabel(""));
|
|
||||||
add(getBiasTCheckBox(), "wrap");
|
|
||||||
|
|
||||||
add(new JSeparator(), "span,growx,push");
|
add(new JSeparator(), "span,growx,push");
|
||||||
add(new JLabel("Correction"), "span,align center");
|
add(new JLabel("Correction"), "span,align center");
|
||||||
@@ -216,37 +209,6 @@ public class FCD1TunerEditor extends TunerEditor<FCDTuner,FCD1TunerConfiguration
|
|||||||
return mLnaGainCombo;
|
return mLnaGainCombo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checkbox to enable/disable Bias-T power for active antennas
|
|
||||||
*/
|
|
||||||
public JCheckBox getBiasTCheckBox()
|
|
||||||
{
|
|
||||||
if(mBiasTCheckBox == null)
|
|
||||||
{
|
|
||||||
mBiasTCheckBox = new JCheckBox("Bias-T");
|
|
||||||
mBiasTCheckBox.setEnabled(false);
|
|
||||||
mBiasTCheckBox.setToolTipText("Enable Bias-T power output for active antennas");
|
|
||||||
mBiasTCheckBox.addActionListener(event ->
|
|
||||||
{
|
|
||||||
if(hasTuner() && !isLoading())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getController().setBiasT(mBiasTCheckBox.isSelected());
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
catch(SourceException e)
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't set bias-T for FCD Pro", e);
|
|
||||||
JOptionPane.showMessageDialog(FCD1TunerEditor.this, "Couldn't set Bias-T: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mBiasTCheckBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JComboBox getLnaEnhanceCombo()
|
public JComboBox getLnaEnhanceCombo()
|
||||||
{
|
{
|
||||||
if(mLnaEnhanceCombo == null)
|
if(mLnaEnhanceCombo == null)
|
||||||
@@ -424,7 +386,6 @@ public class FCD1TunerEditor extends TunerEditor<FCDTuner,FCD1TunerConfiguration
|
|||||||
getConfiguration().setLNAEnhance((LNAEnhance) mLnaEnhanceCombo.getSelectedItem());
|
getConfiguration().setLNAEnhance((LNAEnhance) mLnaEnhanceCombo.getSelectedItem());
|
||||||
getConfiguration().setLNAGain((LNAGain) mLnaGainCombo.getSelectedItem());
|
getConfiguration().setLNAGain((LNAGain) mLnaGainCombo.getSelectedItem());
|
||||||
getConfiguration().setMixerGain((MixerGain) mMixerGainCombo.getSelectedItem());
|
getConfiguration().setMixerGain((MixerGain) mMixerGainCombo.getSelectedItem());
|
||||||
getConfiguration().setBiasT(getBiasTCheckBox().isSelected());
|
|
||||||
double dci = ((SpinnerNumberModel)mCorrectionDCI.getModel()).getNumber().doubleValue();
|
double dci = ((SpinnerNumberModel)mCorrectionDCI.getModel()).getNumber().doubleValue();
|
||||||
getConfiguration().setInphaseDCCorrection(dci);
|
getConfiguration().setInphaseDCCorrection(dci);
|
||||||
double dcq = ((SpinnerNumberModel)mCorrectionDCQ.getModel()).getNumber().doubleValue();
|
double dcq = ((SpinnerNumberModel)mCorrectionDCQ.getModel()).getNumber().doubleValue();
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ public class FCD2TunerConfiguration extends TunerConfiguration
|
|||||||
{
|
{
|
||||||
private boolean mGainLNA = true;
|
private boolean mGainLNA = true;
|
||||||
private boolean mGainMixer = true;
|
private boolean mGainMixer = true;
|
||||||
private boolean mBiasT = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor for JAXB
|
* Default constructor for JAXB
|
||||||
@@ -70,15 +69,4 @@ public class FCD2TunerConfiguration extends TunerConfiguration
|
|||||||
{
|
{
|
||||||
mGainMixer = gain;
|
mGainMixer = gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "bias_t")
|
|
||||||
public boolean isBiasT()
|
|
||||||
{
|
|
||||||
return mBiasT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBiasT(boolean enabled)
|
|
||||||
{
|
|
||||||
mBiasT = enabled;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ public class FCD2TunerController extends FCDTunerController
|
|||||||
public static final int MINIMUM_TUNABLE_FREQUENCY_HZ = 150000;
|
public static final int MINIMUM_TUNABLE_FREQUENCY_HZ = 150000;
|
||||||
public static final int MAXIMUM_TUNABLE_FREQUENCY_HZ = 2050000000;
|
public static final int MAXIMUM_TUNABLE_FREQUENCY_HZ = 2050000000;
|
||||||
public static final int SAMPLE_RATE = 192000;
|
public static final int SAMPLE_RATE = 192000;
|
||||||
private boolean mBiasTEnabled = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -99,48 +98,6 @@ public class FCD2TunerController extends FCDTunerController
|
|||||||
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
|
send(FCDCommand.APP_SET_MIXER_GAIN, enabled ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables/disables the Bias-T (RF bias tee) power output for active antennas
|
|
||||||
*
|
|
||||||
* @param enabled true to enable Bias-T, false to disable
|
|
||||||
* @throws SourceException on error
|
|
||||||
*/
|
|
||||||
public void setBiasT(boolean enabled) throws SourceException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
send(FCDCommand.APP_SET_BIAS_TEE, enabled ? 1 : 0);
|
|
||||||
mBiasTEnabled = enabled;
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
throw new SourceException("Couldn't set FCD Pro Plus bias-T", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns off the bias-T, when enabled, before closing the device.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void stop()
|
|
||||||
{
|
|
||||||
if(mBiasTEnabled)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setBiasT(false);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
mLog.warn("Error turning off FCD Pro Plus bias-T before closing the device - " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
mBiasTEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(TunerConfiguration config) throws SourceException
|
public void apply(TunerConfiguration config) throws SourceException
|
||||||
{
|
{
|
||||||
@@ -151,7 +108,6 @@ public class FCD2TunerController extends FCDTunerController
|
|||||||
{
|
{
|
||||||
setLNAGain(fcd2.getGainLNA());
|
setLNAGain(fcd2.getGainLNA());
|
||||||
setMixerGain(fcd2.getGainMixer());
|
setMixerGain(fcd2.getGainMixer());
|
||||||
setBiasT(fcd2.isBiasT());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import javax.swing.JButton;
|
|||||||
import javax.swing.JCheckBox;
|
import javax.swing.JCheckBox;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JSeparator;
|
import javax.swing.JSeparator;
|
||||||
import javax.swing.SpinnerNumberModel;
|
import javax.swing.SpinnerNumberModel;
|
||||||
|
|
||||||
@@ -46,7 +45,6 @@ public class FCD2TunerEditor extends TunerEditor<FCDTuner, FCD2TunerConfiguratio
|
|||||||
private JButton mTunerInfoButton;
|
private JButton mTunerInfoButton;
|
||||||
private JCheckBox mLnaGainCheckBox;
|
private JCheckBox mLnaGainCheckBox;
|
||||||
private JCheckBox mMixerGainCheckBox;
|
private JCheckBox mMixerGainCheckBox;
|
||||||
private JCheckBox mBiasTCheckBox;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -108,13 +106,11 @@ public class FCD2TunerEditor extends TunerEditor<FCDTuner, FCD2TunerConfiguratio
|
|||||||
getTunerInfoButton().setEnabled(hasTuner());
|
getTunerInfoButton().setEnabled(hasTuner());
|
||||||
getLnaGainCheckBox().setEnabled(hasTuner());
|
getLnaGainCheckBox().setEnabled(hasTuner());
|
||||||
getMixerGainCheckBox().setEnabled(hasTuner());
|
getMixerGainCheckBox().setEnabled(hasTuner());
|
||||||
getBiasTCheckBox().setEnabled(hasTuner());
|
|
||||||
|
|
||||||
if(hasTuner() && hasConfiguration())
|
if(hasTuner() && hasConfiguration())
|
||||||
{
|
{
|
||||||
getLnaGainCheckBox().setSelected(getConfiguration().getGainLNA());
|
getLnaGainCheckBox().setSelected(getConfiguration().getGainLNA());
|
||||||
getMixerGainCheckBox().setSelected(getConfiguration().getGainMixer());
|
getMixerGainCheckBox().setSelected(getConfiguration().getGainMixer());
|
||||||
getBiasTCheckBox().setSelected(getConfiguration().isBiasT());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -139,12 +135,8 @@ public class FCD2TunerEditor extends TunerEditor<FCDTuner, FCD2TunerConfiguratio
|
|||||||
add(getFrequencyPanel(), "wrap");
|
add(getFrequencyPanel(), "wrap");
|
||||||
|
|
||||||
add(new JSeparator(), "span,growx,push");
|
add(new JSeparator(), "span,growx,push");
|
||||||
JPanel checkBoxPanel = new JPanel();
|
add(getLnaGainCheckBox());
|
||||||
checkBoxPanel.setLayout(new MigLayout("insets 0", "[left][left][left]", ""));
|
add(getMixerGainCheckBox());
|
||||||
checkBoxPanel.add(getLnaGainCheckBox());
|
|
||||||
checkBoxPanel.add(getMixerGainCheckBox());
|
|
||||||
checkBoxPanel.add(getBiasTCheckBox());
|
|
||||||
add(checkBoxPanel, "span,align left");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public JButton getTunerInfoButton()
|
public JButton getTunerInfoButton()
|
||||||
@@ -212,37 +204,6 @@ public class FCD2TunerEditor extends TunerEditor<FCDTuner, FCD2TunerConfiguratio
|
|||||||
return mMixerGainCheckBox;
|
return mMixerGainCheckBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checkbox to enable/disable Bias-T power for active antennas
|
|
||||||
*/
|
|
||||||
public JCheckBox getBiasTCheckBox()
|
|
||||||
{
|
|
||||||
if(mBiasTCheckBox == null)
|
|
||||||
{
|
|
||||||
mBiasTCheckBox = new JCheckBox("Bias-T");
|
|
||||||
mBiasTCheckBox.setEnabled(false);
|
|
||||||
mBiasTCheckBox.setToolTipText("Enable Bias-T power output for active antennas");
|
|
||||||
mBiasTCheckBox.addActionListener(event ->
|
|
||||||
{
|
|
||||||
if(hasTuner() && !isLoading())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getController().setBiasT(getBiasTCheckBox().isSelected());
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
catch(SourceException e)
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't set bias-T for FCD2", e);
|
|
||||||
JOptionPane.showMessageDialog(mBiasTCheckBox, "Couldn't set Bias-T: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mBiasTCheckBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTunerLockState(boolean locked)
|
public void setTunerLockState(boolean locked)
|
||||||
{
|
{
|
||||||
@@ -294,7 +255,6 @@ public class FCD2TunerEditor extends TunerEditor<FCDTuner, FCD2TunerConfiguratio
|
|||||||
getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
||||||
getConfiguration().setGainLNA(getLnaGainCheckBox().isSelected());
|
getConfiguration().setGainLNA(getLnaGainCheckBox().isSelected());
|
||||||
getConfiguration().setGainMixer(getMixerGainCheckBox().isSelected());
|
getConfiguration().setGainMixer(getMixerGainCheckBox().isSelected());
|
||||||
getConfiguration().setBiasT(getBiasTCheckBox().isSelected());
|
|
||||||
saveConfiguration();
|
saveConfiguration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,17 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
|||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.UsbDeviceLookup;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.usb4java.Context;
|
||||||
|
import org.usb4java.Device;
|
||||||
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
import org.usb4java.DeviceHandle;
|
||||||
|
import org.usb4java.DeviceList;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HackRF One, HackRF Pro, Jawbreaker and rad1o tuner controller, implemented with the jHackRF library (libhackrf).
|
* HackRF One, HackRF Pro, Jawbreaker and rad1o tuner controller, implemented with the jHackRF library (libhackrf).
|
||||||
@@ -246,21 +252,110 @@ public class HackRFTunerController extends TunerController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the USB serial number of the device at this controller's USB bus and port, since libhackrf opens a
|
* Reads the USB serial number of the device at this controller's USB bus and port, since libhackrf opens a
|
||||||
* specific device by serial number.
|
* specific device by serial number. The device is closed again before returning so that libhackrf can open it.
|
||||||
* @return serial number or null if the device doesn't report a serial number.
|
* @return serial number or null if the device doesn't report a serial number.
|
||||||
* @throws SourceException if the device isn't found or can't be accessed.
|
* @throws SourceException if the device isn't found or can't be accessed.
|
||||||
*/
|
*/
|
||||||
private String readSerialNumber() throws SourceException
|
private String readSerialNumber() throws SourceException
|
||||||
{
|
{
|
||||||
String serial = UsbDeviceLookup.readSerialNumber(mBus, mPortAddress);
|
Context context = new Context();
|
||||||
|
int status = LibUsb.init(context);
|
||||||
|
|
||||||
//libhackrf compares, at most, the last 32 characters of the serial string
|
if(status != LibUsb.SUCCESS)
|
||||||
if(serial != null && serial.length() > 32)
|
|
||||||
{
|
{
|
||||||
serial = serial.substring(serial.length() - 32);
|
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
return serial == null || serial.isEmpty() ? null : serial;
|
try
|
||||||
|
{
|
||||||
|
DeviceList deviceList = new DeviceList();
|
||||||
|
int count = LibUsb.getDeviceList(context, deviceList);
|
||||||
|
|
||||||
|
if(count < 0)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(Device device : deviceList)
|
||||||
|
{
|
||||||
|
if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 &&
|
||||||
|
mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device)))
|
||||||
|
{
|
||||||
|
return readSerialNumber(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.freeDeviceList(deviceList, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.exit(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the serial number from the device's USB serial string descriptor.
|
||||||
|
* @param device to read
|
||||||
|
* @return serial number or null if the device doesn't have a serial string.
|
||||||
|
* @throws SourceException if the device can't be opened.
|
||||||
|
*/
|
||||||
|
private static String readSerialNumber(Device device) throws SourceException
|
||||||
|
{
|
||||||
|
DeviceDescriptor descriptor = new DeviceDescriptor();
|
||||||
|
int status = LibUsb.getDeviceDescriptor(device, descriptor);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(descriptor.iSerialNumber() == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceHandle handle = new DeviceHandle();
|
||||||
|
status = LibUsb.open(device, handle);
|
||||||
|
|
||||||
|
if(status == LibUsb.ERROR_ACCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules");
|
||||||
|
throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules");
|
||||||
|
}
|
||||||
|
else if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Can't open USB tuner - check driver or Linux udev rules");
|
||||||
|
throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String serial = LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber());
|
||||||
|
|
||||||
|
if(serial != null)
|
||||||
|
{
|
||||||
|
serial = serial.trim();
|
||||||
|
|
||||||
|
//libhackrf compares, at most, the last 32 characters of the serial string
|
||||||
|
if(serial.length() > 32)
|
||||||
|
{
|
||||||
|
serial = serial.substring(serial.length() - 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serial == null || serial.isEmpty() ? null : serial;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.close(handle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,13 +34,19 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
|||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.UsbDeviceLookup;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.usb4java.Context;
|
||||||
|
import org.usb4java.Device;
|
||||||
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
import org.usb4java.DeviceHandle;
|
||||||
|
import org.usb4java.DeviceList;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HydraSDR tuner controller, implemented with the jHydraSDR library (libhydrasdr).
|
* HydraSDR tuner controller, implemented with the jHydraSDR library (libhydrasdr).
|
||||||
@@ -203,17 +209,98 @@ public class HydraSdrTunerController extends TunerController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the serial number of the device at this controller's USB bus and port, since libhydrasdr opens a specific
|
* Reads the serial number of the device at this controller's USB bus and port, since libhydrasdr opens a specific
|
||||||
* device by serial number. The USB serial string is formatted as: HYDRASDR SN:0123456789ABCDEF
|
* device by serial number. The device is closed again before returning so that libhydrasdr can open it.
|
||||||
* @return serial number
|
* @return serial number
|
||||||
* @throws SourceException if the device isn't found or can't be accessed.
|
* @throws SourceException if the device isn't found or can't be accessed.
|
||||||
*/
|
*/
|
||||||
private long readSerialNumber() throws SourceException
|
private long readSerialNumber() throws SourceException
|
||||||
{
|
{
|
||||||
String serial = UsbDeviceLookup.readSerialNumber(mBus, mPortAddress);
|
Context context = new Context();
|
||||||
|
int status = LibUsb.init(context);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Long.parseUnsignedLong(serial.substring(serial.lastIndexOf(':') + 1).trim(), 16);
|
DeviceList deviceList = new DeviceList();
|
||||||
|
int count = LibUsb.getDeviceList(context, deviceList);
|
||||||
|
|
||||||
|
if(count < 0)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(Device device : deviceList)
|
||||||
|
{
|
||||||
|
if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 &&
|
||||||
|
mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device)))
|
||||||
|
{
|
||||||
|
return readSerialNumber(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.freeDeviceList(deviceList, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.exit(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the serial number from the device's USB serial string, formatted as: HYDRASDR SN:0123456789ABCDEF
|
||||||
|
* @param device to read
|
||||||
|
* @return serial number
|
||||||
|
* @throws SourceException if the device can't be opened or the serial string isn't valid.
|
||||||
|
*/
|
||||||
|
private static long readSerialNumber(Device device) throws SourceException
|
||||||
|
{
|
||||||
|
DeviceDescriptor descriptor = new DeviceDescriptor();
|
||||||
|
int status = LibUsb.getDeviceDescriptor(device, descriptor);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
throw new SourceException("Can't obtain tuner's device descriptor - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceHandle handle = new DeviceHandle();
|
||||||
|
status = LibUsb.open(device, handle);
|
||||||
|
|
||||||
|
if(status == LibUsb.ERROR_ACCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules");
|
||||||
|
throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules");
|
||||||
|
}
|
||||||
|
else if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Can't open USB tuner - check driver or Linux udev rules");
|
||||||
|
throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
String serial;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serial = LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.close(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Long.parseUnsignedLong(serial.substring(serial.lastIndexOf(':') + 1), 16);
|
||||||
}
|
}
|
||||||
catch(NullPointerException | NumberFormatException e)
|
catch(NullPointerException | NumberFormatException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.limesdr;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LimeSDR device details for display.
|
|
||||||
* @param serial board serial number, 16 hex digits
|
|
||||||
* @param board name, such as LimeSDR-USB or LimeSDR-Mini
|
|
||||||
* @param firmware version
|
|
||||||
* @param hardware version
|
|
||||||
* @param gateware (FPGA) version
|
|
||||||
* @param connection USB link, such as USB3.0
|
|
||||||
* @param library version of LimeSuiteNG
|
|
||||||
*/
|
|
||||||
public record LimeSdrDeviceInformation(String serial, String board, String firmware, String hardware, String gateware,
|
|
||||||
String connection, String library)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.limesdr;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LimeSDR sample rates offered to the user. The LMS7002M supports any rate in its range, so these are rates that divide
|
|
||||||
* evenly into polyphase channelizer channels.
|
|
||||||
*/
|
|
||||||
public enum LimeSdrSampleRate
|
|
||||||
{
|
|
||||||
RATE_1_25(1_250_000, "1.25 MHz"),
|
|
||||||
RATE_2_5(2_500_000, "2.50 MHz"),
|
|
||||||
RATE_5_0(5_000_000, "5.00 MHz"),
|
|
||||||
RATE_7_5(7_500_000, "7.50 MHz"),
|
|
||||||
RATE_10_0(10_000_000, "10.00 MHz"),
|
|
||||||
RATE_12_5(12_500_000, "12.50 MHz"),
|
|
||||||
RATE_15_0(15_000_000, "15.00 MHz"),
|
|
||||||
RATE_20_0(20_000_000, "20.00 MHz"),
|
|
||||||
RATE_25_0(25_000_000, "25.00 MHz"),
|
|
||||||
RATE_30_0(30_000_000, "30.00 MHz"),
|
|
||||||
RATE_30_72(30_720_000, "30.72 MHz"),
|
|
||||||
RATE_40_0(40_000_000, "40.00 MHz"),
|
|
||||||
RATE_50_0(50_000_000, "50.00 MHz"),
|
|
||||||
RATE_60_0(60_000_000, "60.00 MHz"),
|
|
||||||
RATE_61_44(61_440_000, "61.44 MHz");
|
|
||||||
|
|
||||||
public static final LimeSdrSampleRate DEFAULT = RATE_10_0;
|
|
||||||
private final int mRate;
|
|
||||||
private final String mLabel;
|
|
||||||
|
|
||||||
LimeSdrSampleRate(int rate, String label)
|
|
||||||
{
|
|
||||||
mRate = rate;
|
|
||||||
mLabel = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sample rate in Hertz
|
|
||||||
*/
|
|
||||||
public int getRate()
|
|
||||||
{
|
|
||||||
return mRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return mLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sample rates within the device range, which is the lower of what the board and its USB connection support.
|
|
||||||
* @param minimumRate in Hertz
|
|
||||||
* @param maximumRate in Hertz
|
|
||||||
* @return rates, lowest to highest
|
|
||||||
*/
|
|
||||||
public static List<LimeSdrSampleRate> getRates(double minimumRate, double maximumRate)
|
|
||||||
{
|
|
||||||
List<LimeSdrSampleRate> rates = new ArrayList<>();
|
|
||||||
|
|
||||||
for(LimeSdrSampleRate rate : values())
|
|
||||||
{
|
|
||||||
if(rate.getRate() >= minimumRate && rate.getRate() <= maximumRate)
|
|
||||||
{
|
|
||||||
rates.add(rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the highest rate from the list that doesn't exceed the requested rate.
|
|
||||||
* @param rates to search, lowest to highest
|
|
||||||
* @param requested rate in Hertz
|
|
||||||
* @return closest rate, or the lowest rate when the requested rate is below all of them.
|
|
||||||
*/
|
|
||||||
public static LimeSdrSampleRate getClosest(List<LimeSdrSampleRate> rates, int requested)
|
|
||||||
{
|
|
||||||
LimeSdrSampleRate closest = rates.getFirst();
|
|
||||||
|
|
||||||
for(LimeSdrSampleRate rate : rates)
|
|
||||||
{
|
|
||||||
if(rate.getRate() <= requested)
|
|
||||||
{
|
|
||||||
closest = rate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return closest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.limesdr;
|
|
||||||
|
|
||||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
|
||||||
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
|
||||||
import io.github.dsheirer.source.tuner.Tuner;
|
|
||||||
import io.github.dsheirer.source.tuner.TunerClass;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LimeSDR tuner
|
|
||||||
*/
|
|
||||||
public class LimeSdrTuner extends Tuner
|
|
||||||
{
|
|
||||||
//The USB link carries 12-bit I and Q samples, packed into 3 bytes
|
|
||||||
private static final int LINK_BITS_PER_SAMPLE = 24;
|
|
||||||
private static final double SAMPLE_SIZE_12_BIT = 12.0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
* @param controller for the LimeSDR
|
|
||||||
* @param tunerErrorListener to listen for errors from this tuner
|
|
||||||
* @param channelizerType for the channelizer
|
|
||||||
*/
|
|
||||||
public LimeSdrTuner(LimeSdrTunerController controller, ITunerErrorListener tunerErrorListener,
|
|
||||||
ChannelizerType channelizerType)
|
|
||||||
{
|
|
||||||
super(controller, tunerErrorListener, channelizerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LimeSDR tuner controller
|
|
||||||
*/
|
|
||||||
public LimeSdrTunerController getController()
|
|
||||||
{
|
|
||||||
return (LimeSdrTunerController)getTunerController();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPreferredName()
|
|
||||||
{
|
|
||||||
LimeSdrDeviceInformation info = getController().getDeviceInfo();
|
|
||||||
return (info != null ? info.board() : "LimeSDR") + " " + getUniqueID();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUniqueID()
|
|
||||||
{
|
|
||||||
return getController().getDeviceInfo().serial();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TunerClass getTunerClass()
|
|
||||||
{
|
|
||||||
return TunerClass.LIMESDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public double getSampleSize()
|
|
||||||
{
|
|
||||||
return SAMPLE_SIZE_12_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaximumUSBBitsPerSecond()
|
|
||||||
{
|
|
||||||
return LimeSdrSampleRate.RATE_61_44.getRate() * LINK_BITS_PER_SAMPLE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.limesdr;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LimeSDR tuner configuration
|
|
||||||
*/
|
|
||||||
public class LimeSdrTunerConfiguration extends TunerConfiguration
|
|
||||||
{
|
|
||||||
private int mSampleRate = LimeSdrSampleRate.DEFAULT.getRate();
|
|
||||||
private int mGain = LimeSdrTunerController.GAIN_DEFAULT;
|
|
||||||
private String mAntenna = LimeSdrTunerController.ANTENNA_DEFAULT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default constructor for Jackson
|
|
||||||
*/
|
|
||||||
public LimeSdrTunerConfiguration()
|
|
||||||
{
|
|
||||||
super(LimeSdrTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ, LimeSdrTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
* @param uniqueID for the tuner
|
|
||||||
*/
|
|
||||||
public LimeSdrTunerConfiguration(String uniqueID)
|
|
||||||
{
|
|
||||||
super(uniqueID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
|
||||||
public TunerType getTunerType()
|
|
||||||
{
|
|
||||||
return TunerType.LIMESDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
|
|
||||||
public int getSampleRate()
|
|
||||||
{
|
|
||||||
return mSampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSampleRate(int sampleRate)
|
|
||||||
{
|
|
||||||
mSampleRate = sampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combined receive gain in dB, 0 to 73.
|
|
||||||
*/
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "gain")
|
|
||||||
public int getGain()
|
|
||||||
{
|
|
||||||
return mGain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGain(int gain)
|
|
||||||
{
|
|
||||||
mGain = gain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive antenna port name, such as LNAW.
|
|
||||||
*/
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "antenna")
|
|
||||||
public String getAntenna()
|
|
||||||
{
|
|
||||||
return mAntenna;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAntenna(String antenna)
|
|
||||||
{
|
|
||||||
mAntenna = antenna;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,363 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.limesdr;
|
|
||||||
|
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
|
||||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerStatus;
|
|
||||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
|
||||||
import java.io.Serial;
|
|
||||||
import java.util.List;
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.DefaultComboBoxModel;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JComboBox;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
import javax.swing.JSeparator;
|
|
||||||
import javax.swing.JSlider;
|
|
||||||
import javax.swing.SpinnerNumberModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LimeSDR tuner editor
|
|
||||||
*/
|
|
||||||
public class LimeSdrTunerEditor extends TunerEditor<LimeSdrTuner, LimeSdrTunerConfiguration>
|
|
||||||
{
|
|
||||||
@Serial
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(LimeSdrTunerEditor.class);
|
|
||||||
private JButton mTunerInfoButton;
|
|
||||||
private JComboBox<LimeSdrSampleRate> mSampleRateCombo;
|
|
||||||
private JComboBox<String> mAntennaCombo;
|
|
||||||
private JSlider mGainSlider;
|
|
||||||
private JLabel mGainValueLabel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
* @param userPreferences for wide-band recordings
|
|
||||||
* @param tunerManager for saving tuner configuration
|
|
||||||
* @param discoveredTuner for the optionally usable LimeSDR tuner and controller
|
|
||||||
*/
|
|
||||||
public LimeSdrTunerEditor(UserPreferences userPreferences, TunerManager tunerManager, DiscoveredTuner discoveredTuner)
|
|
||||||
{
|
|
||||||
super(userPreferences, tunerManager, discoveredTuner);
|
|
||||||
init();
|
|
||||||
tunerStatusUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMinimumTunableFrequency()
|
|
||||||
{
|
|
||||||
return hasTuner() ? getTuner().getController().getMinimumTunableFrequency() :
|
|
||||||
LimeSdrTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getMaximumTunableFrequency()
|
|
||||||
{
|
|
||||||
return hasTuner() ? getTuner().getController().getMaximumTunableFrequency() :
|
|
||||||
LimeSdrTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init()
|
|
||||||
{
|
|
||||||
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]", "[][][][][][][][][][][grow]"));
|
|
||||||
|
|
||||||
add(new JLabel("Tuner:"));
|
|
||||||
add(getTunerIdLabel());
|
|
||||||
add(getTunerInfoButton());
|
|
||||||
|
|
||||||
add(new JLabel("Status:"));
|
|
||||||
add(getTunerStatusLabel(), "wrap");
|
|
||||||
|
|
||||||
add(getButtonPanel(), "span,align left");
|
|
||||||
|
|
||||||
add(new JSeparator(), "span,growx");
|
|
||||||
|
|
||||||
add(new JLabel("Frequency (MHz):"));
|
|
||||||
add(getFrequencyPanel(), "wrap");
|
|
||||||
|
|
||||||
add(new JLabel("Sample Rate:"));
|
|
||||||
add(getSampleRateCombo(), "wrap");
|
|
||||||
|
|
||||||
add(new JLabel("Antenna:"));
|
|
||||||
add(getAntennaCombo(), "wrap");
|
|
||||||
|
|
||||||
add(new JSeparator(), "span,growx");
|
|
||||||
|
|
||||||
add(new JLabel("Gain:"));
|
|
||||||
add(getGainSlider());
|
|
||||||
add(getGainValueLabel());
|
|
||||||
|
|
||||||
//Empty row that takes all extra vertical space so that the controls stay stacked at the top of the editor.
|
|
||||||
add(new JLabel(), "span,pushy");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tunerStatusUpdated()
|
|
||||||
{
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
boolean started = hasStartedTuner();
|
|
||||||
getTunerIdLabel().setText(started ? getTuner().getPreferredName() : getDiscoveredTuner().getId());
|
|
||||||
|
|
||||||
String status = getDiscoveredTuner().getTunerStatus().toString();
|
|
||||||
if(getDiscoveredTuner().hasErrorMessage())
|
|
||||||
{
|
|
||||||
status += " - " + getDiscoveredTuner().getErrorMessage();
|
|
||||||
}
|
|
||||||
getTunerStatusLabel().setText(status);
|
|
||||||
getButtonPanel().updateControls();
|
|
||||||
getFrequencyPanel().updateControls();
|
|
||||||
getTunerInfoButton().setEnabled(started);
|
|
||||||
|
|
||||||
if(started)
|
|
||||||
{
|
|
||||||
LimeSdrTunerController controller = getTuner().getController();
|
|
||||||
List<LimeSdrSampleRate> rates = controller.getSampleRates();
|
|
||||||
getSampleRateCombo().setModel(new DefaultComboBoxModel<>(rates.toArray(new LimeSdrSampleRate[0])));
|
|
||||||
getSampleRateCombo().setSelectedItem(controller.getLimeSdrSampleRate());
|
|
||||||
getSampleRateCombo().setEnabled(!controller.isLockedSampleRate());
|
|
||||||
|
|
||||||
List<String> antennas = controller.getAntennas();
|
|
||||||
getAntennaCombo().setModel(new DefaultComboBoxModel<>(antennas.toArray(new String[0])));
|
|
||||||
getAntennaCombo().setSelectedItem(controller.getAntenna());
|
|
||||||
getAntennaCombo().setEnabled(true);
|
|
||||||
|
|
||||||
getGainSlider().setValue(controller.getGain());
|
|
||||||
getGainSlider().setEnabled(true);
|
|
||||||
getGainValueLabel().setEnabled(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
getSampleRateCombo().setModel(new DefaultComboBoxModel<>());
|
|
||||||
getSampleRateCombo().setEnabled(false);
|
|
||||||
getAntennaCombo().setModel(new DefaultComboBoxModel<>());
|
|
||||||
getAntennaCombo().setEnabled(false);
|
|
||||||
getGainSlider().setEnabled(false);
|
|
||||||
getGainValueLabel().setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGainLabel();
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the tuner has finished starting, since a status update can arrive while it's starting or restarting.
|
|
||||||
*/
|
|
||||||
private boolean hasStartedTuner()
|
|
||||||
{
|
|
||||||
return hasTuner() && getTuner().getController().getAntenna() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateGainLabel()
|
|
||||||
{
|
|
||||||
getGainValueLabel().setText(getGainSlider().getValue() + " dB");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Button that provides tuner information
|
|
||||||
*/
|
|
||||||
private JButton getTunerInfoButton()
|
|
||||||
{
|
|
||||||
if(mTunerInfoButton == null)
|
|
||||||
{
|
|
||||||
mTunerInfoButton = new JButton("Info");
|
|
||||||
mTunerInfoButton.setEnabled(false);
|
|
||||||
mTunerInfoButton.addActionListener(_ -> JOptionPane.showMessageDialog(LimeSdrTunerEditor.this,
|
|
||||||
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
|
||||||
}
|
|
||||||
|
|
||||||
return mTunerInfoButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JComboBox<LimeSdrSampleRate> getSampleRateCombo()
|
|
||||||
{
|
|
||||||
if(mSampleRateCombo == null)
|
|
||||||
{
|
|
||||||
mSampleRateCombo = new JComboBox<>();
|
|
||||||
mSampleRateCombo.setEnabled(false);
|
|
||||||
mSampleRateCombo.addActionListener(_ ->
|
|
||||||
{
|
|
||||||
if(hasTuner() && !isLoading())
|
|
||||||
{
|
|
||||||
LimeSdrSampleRate rate = (LimeSdrSampleRate)mSampleRateCombo.getSelectedItem();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getTuner().getController().setSampleRate(rate);
|
|
||||||
|
|
||||||
//Adjust the min/max values for the sample rate.
|
|
||||||
adjustForSampleRate(rate.getRate());
|
|
||||||
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
catch(Exception e1)
|
|
||||||
{
|
|
||||||
mLog.error("Error setting LimeSDR sample rate", e1);
|
|
||||||
JOptionPane.showMessageDialog(LimeSdrTunerEditor.this, "Couldn't set sample rate to " + rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mSampleRateCombo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JComboBox<String> getAntennaCombo()
|
|
||||||
{
|
|
||||||
if(mAntennaCombo == null)
|
|
||||||
{
|
|
||||||
mAntennaCombo = new JComboBox<>();
|
|
||||||
mAntennaCombo.setEnabled(false);
|
|
||||||
mAntennaCombo.setToolTipText("Receive port: LNAW wideband (default), LNAL low band, LNAH high band");
|
|
||||||
mAntennaCombo.addActionListener(_ ->
|
|
||||||
{
|
|
||||||
if(hasTuner() && !isLoading())
|
|
||||||
{
|
|
||||||
String antenna = (String)mAntennaCombo.getSelectedItem();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getTuner().getController().setAntenna(antenna);
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
catch(Exception e1)
|
|
||||||
{
|
|
||||||
mLog.error("Error setting LimeSDR antenna", e1);
|
|
||||||
JOptionPane.showMessageDialog(mAntennaCombo, "Couldn't set antenna to " + antenna);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mAntennaCombo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSlider getGainSlider()
|
|
||||||
{
|
|
||||||
if(mGainSlider == null)
|
|
||||||
{
|
|
||||||
mGainSlider = new JSlider(JSlider.HORIZONTAL, LimeSdrTunerController.GAIN_MINIMUM,
|
|
||||||
LimeSdrTunerController.GAIN_MAXIMUM, LimeSdrTunerController.GAIN_DEFAULT);
|
|
||||||
mGainSlider.setEnabled(false);
|
|
||||||
mGainSlider.setMajorTickSpacing(5);
|
|
||||||
mGainSlider.setPaintTicks(true);
|
|
||||||
mGainSlider.addChangeListener(_ ->
|
|
||||||
{
|
|
||||||
int gain = mGainSlider.getValue();
|
|
||||||
|
|
||||||
if(hasTuner() && !isLoading())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
getTuner().getController().setGain(gain);
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't set LimeSDR gain to:" + gain, e);
|
|
||||||
JOptionPane.showMessageDialog(mGainSlider, "Couldn't set gain value to " + gain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGainLabel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mGainSlider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JLabel getGainValueLabel()
|
|
||||||
{
|
|
||||||
if(mGainValueLabel == null)
|
|
||||||
{
|
|
||||||
mGainValueLabel = new JLabel("0 dB");
|
|
||||||
mGainValueLabel.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mGainValueLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save()
|
|
||||||
{
|
|
||||||
if(hasConfiguration() && !isLoading())
|
|
||||||
{
|
|
||||||
getConfiguration().setFrequency(getFrequencyControl().getFrequency());
|
|
||||||
getConfiguration().setMinimumFrequency(getMinimumFrequencyTextField().getFrequency());
|
|
||||||
getConfiguration().setMaximumFrequency(getMaximumFrequencyTextField().getFrequency());
|
|
||||||
double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
|
||||||
getConfiguration().setFrequencyCorrection(value);
|
|
||||||
getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
|
||||||
|
|
||||||
if(getSampleRateCombo().getSelectedItem() instanceof LimeSdrSampleRate rate)
|
|
||||||
{
|
|
||||||
getConfiguration().setSampleRate(rate.getRate());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(getAntennaCombo().getSelectedItem() instanceof String antenna)
|
|
||||||
{
|
|
||||||
getConfiguration().setAntenna(antenna);
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfiguration().setGain(getGainSlider().getValue());
|
|
||||||
saveConfiguration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTunerLockState(boolean locked)
|
|
||||||
{
|
|
||||||
getFrequencyPanel().updateControls();
|
|
||||||
getSampleRateCombo().setEnabled(!locked);
|
|
||||||
getSampleRateCombo().setToolTipText(locked ? "Sample Rate is locked. Disable decoding channels to unlock." :
|
|
||||||
"Select a sample rate for the tuner");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTunerInfo()
|
|
||||||
{
|
|
||||||
if(getDiscoveredTuner().getTunerStatus() == TunerStatus.ERROR)
|
|
||||||
{
|
|
||||||
return getDiscoveredTuner().getErrorMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hasTuner())
|
|
||||||
{
|
|
||||||
LimeSdrDeviceInformation info = getTuner().getController().getDeviceInfo();
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("<html><h3>LimeSDR Tuner</h3>");
|
|
||||||
sb.append("<b>Board: </b>").append(info.board()).append("<br>");
|
|
||||||
sb.append("<b>Serial: </b>").append(info.serial()).append("<br>");
|
|
||||||
sb.append("<b>Hardware: </b>").append(info.hardware()).append("<br>");
|
|
||||||
sb.append("<b>Firmware: </b>").append(info.firmware()).append("<br>");
|
|
||||||
sb.append("<b>Gateware: </b>").append(info.gateware()).append("<br>");
|
|
||||||
sb.append("<b>Connection: </b>").append(info.connection()).append("<br>");
|
|
||||||
sb.append("<b>LimeSuiteNG: </b>").append(info.library()).append("<br>");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,13 +20,6 @@
|
|||||||
package io.github.dsheirer.source.tuner.manager;
|
package io.github.dsheirer.source.tuner.manager;
|
||||||
|
|
||||||
import io.github.dsheirer.gui.preference.tuner.RspDuoSelectionMode;
|
import io.github.dsheirer.gui.preference.tuner.RspDuoSelectionMode;
|
||||||
import io.github.dsheirer.libusb.LibUsb;
|
|
||||||
import io.github.dsheirer.libusb.UsbContext;
|
|
||||||
import io.github.dsheirer.libusb.UsbDeviceInfo;
|
|
||||||
import io.github.dsheirer.libusb.UsbException;
|
|
||||||
import io.github.dsheirer.libusb.UsbHotplug;
|
|
||||||
import io.github.dsheirer.libusb.UsbHotplugListener;
|
|
||||||
import io.github.dsheirer.libusb.UsbLogLevel;
|
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||||
import io.github.dsheirer.sdrplay.DeviceInfo;
|
import io.github.dsheirer.sdrplay.DeviceInfo;
|
||||||
@@ -52,13 +45,22 @@ import io.github.dsheirer.source.tuner.sdrplay.DiscoveredRspTuner;
|
|||||||
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.DiscoveredRspDuoTuner1;
|
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.DiscoveredRspDuoTuner1;
|
||||||
import io.github.dsheirer.source.tuner.ui.DiscoveredTunerModel;
|
import io.github.dsheirer.source.tuner.ui.DiscoveredTunerModel;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.usb4java.Context;
|
||||||
|
import org.usb4java.Device;
|
||||||
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
import org.usb4java.DeviceList;
|
||||||
|
import org.usb4java.HotplugCallback;
|
||||||
|
import org.usb4java.HotplugCallbackHandle;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tuner manager provides access to tuners using USB, recording, sound-card and system-daemon accessible devices. This
|
* Tuner manager provides access to tuners using USB, recording, sound-card and system-daemon accessible devices. This
|
||||||
@@ -71,12 +73,9 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
|||||||
private final UserPreferences mUserPreferences;
|
private final UserPreferences mUserPreferences;
|
||||||
private final DiscoveredTunerModel mDiscoveredTunerModel;
|
private final DiscoveredTunerModel mDiscoveredTunerModel;
|
||||||
private final TunerConfigurationManager mTunerConfigurationManager;
|
private final TunerConfigurationManager mTunerConfigurationManager;
|
||||||
/**
|
|
||||||
* libusb error code for an unspecified error (LIBUSB_ERROR_OTHER)
|
|
||||||
*/
|
|
||||||
private static final int LIBUSB_ERROR_OTHER = -99;
|
|
||||||
private final HotplugEventSupport mHotplugEventSupport = new HotplugEventSupport();
|
private final HotplugEventSupport mHotplugEventSupport = new HotplugEventSupport();
|
||||||
private boolean mLibUsbAvailable = false;
|
private final Context mLibUsbApplicationContext = new Context();
|
||||||
|
private boolean mLibUsbInitialized = false;
|
||||||
private SDRplay mSDRplay;
|
private SDRplay mSDRplay;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,26 +112,60 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
|||||||
{
|
{
|
||||||
mLog.info("Discovering tuners ...");
|
mLog.info("Discovering tuners ...");
|
||||||
|
|
||||||
|
boolean libUsbAvailable = false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Loads the libusb library that jlibusb bundles, which the USB tuner libraries share
|
int api = LibUsb.getApiVersion();
|
||||||
mLog.info("LibUsb Version: " + LibUsb.version() + " - hotplug detection: " +
|
libUsbAvailable = true;
|
||||||
(LibUsb.hasHotplug() ? "native" : "polling"));
|
mLog.info("LibUsb API Version: " + ((api >> 24) & 0xFF) + "." + ((api >> 16) & 0xFF) + "." + (api & 0xFFFF));
|
||||||
mLibUsbAvailable = true;
|
mLog.info("LibUsb Version: " + LibUsb.getVersion());
|
||||||
}
|
}
|
||||||
catch(LinkageError | RuntimeException e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
String os = System.getProperty("os.name");
|
final String os = System.getProperty("os.name").toLowerCase();
|
||||||
String version = System.getProperty("os.version");
|
final String version = System.getProperty("os.version");
|
||||||
mLog.error("Unable to load the libusb library for communication with USB tuner(s) on os [" + os +
|
|
||||||
"] version [" + version + "]. To use a different libusb build, start sdrtrunk with " +
|
//Special handling for MacOS Tahoe version 26 issue with LibUsb.
|
||||||
"-Djlibusb.library.path=<directory containing libusb>", e);
|
if(os != null && os.startsWith("macos") && version != null && version.startsWith("26"))
|
||||||
|
{
|
||||||
|
mLog.warn("Unable to load libusb library for communication with USB tuner(s)");
|
||||||
|
mLog.warn("Mac OS Tahoe [" + os + "] version [" + version + "] detected. There is an issue");
|
||||||
|
mLog.warn("with libusb for this OS version. You might consider manually installing the latest");
|
||||||
|
mLog.warn("version of the libusb library. In a terminal window type:");
|
||||||
|
mLog.warn("");
|
||||||
|
mLog.warn("\t\tbrew install libusb --HEAD");
|
||||||
|
mLog.warn("");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Error while loading libusb for os [" + os + "] version [" + version + "]", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mLibUsbAvailable)
|
if(libUsbAvailable)
|
||||||
{
|
{
|
||||||
discoverUSBTuners();
|
try
|
||||||
mHotplugEventSupport.start();
|
{
|
||||||
|
int status = LibUsb.init(mLibUsbApplicationContext);
|
||||||
|
|
||||||
|
if(status == LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mLibUsbInitialized = true;
|
||||||
|
// LibUsb.setOption(mLibUsbApplicationContext, LibUsb.OPTION_LOG_LEVEL, LibUsb.LOG_LEVEL_DEBUG);
|
||||||
|
discoverUSBTuners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error initializing LibUsb and usb4java library", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mLibUsbInitialized)
|
||||||
|
{
|
||||||
|
mHotplugEventSupport.start();
|
||||||
|
}
|
||||||
|
|
||||||
discoverSdrPlayTuners();
|
discoverSdrPlayTuners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,14 +177,19 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
|||||||
*/
|
*/
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
//Stop hotplug detection first so that no tuner is added or removed while the tuners are released
|
|
||||||
mHotplugEventSupport.stop();
|
|
||||||
|
|
||||||
//Stop all tuners
|
//Stop all tuners
|
||||||
mDiscoveredTunerModel.releaseDiscoveredTuners();
|
mDiscoveredTunerModel.releaseDiscoveredTuners();
|
||||||
|
|
||||||
//Shutdown SDRplay API instance
|
//Shutdown SDRplay API instance
|
||||||
closeSDRplay();
|
closeSDRplay();
|
||||||
|
|
||||||
|
//Shutdown LibUsb
|
||||||
|
if(mLibUsbInitialized)
|
||||||
|
{
|
||||||
|
mHotplugEventSupport.stop();
|
||||||
|
LibUsb.exit(mLibUsbApplicationContext);
|
||||||
|
mLibUsbInitialized = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,125 +205,103 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
|||||||
*/
|
*/
|
||||||
private void discoverUSBTuners()
|
private void discoverUSBTuners()
|
||||||
{
|
{
|
||||||
List<DiscoveredUSBTuner> discoveredUSBTuners = new ArrayList<>();
|
if(mLibUsbInitialized)
|
||||||
|
{
|
||||||
|
List<DiscoveredUSBTuner> discoveredUSBTuners = new ArrayList<>();
|
||||||
|
|
||||||
try(UsbContext usb = UsbContext.create())
|
|
||||||
{
|
|
||||||
List<UsbDeviceInfo> devices = getDeviceList(usb);
|
|
||||||
|
|
||||||
if(devices != null)
|
|
||||||
{
|
|
||||||
mLog.info("LibUsb - discovered [" + devices.size() + "] potential usb devices");
|
|
||||||
|
|
||||||
for(UsbDeviceInfo device: devices)
|
|
||||||
{
|
|
||||||
DiscoveredUSBTuner discoveredUSBTuner = createDiscoveredTuner(device);
|
|
||||||
|
|
||||||
if(discoveredUSBTuner != null)
|
|
||||||
{
|
|
||||||
mLog.info("Discovered tuner at USB Bus [" + discoveredUSBTuner.getBus() + "] Port [" +
|
|
||||||
discoveredUSBTuner.getPortAddress() + "] Tuner Class [" +
|
|
||||||
discoveredUSBTuner.getTunerClass() + "]");
|
|
||||||
discoveredUSBTuners.add(discoveredUSBTuner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(UsbException | RuntimeException e)
|
|
||||||
{
|
|
||||||
mLog.error("LibUsb - error during USB device discovery", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(DiscoveredUSBTuner discoveredUSBTuner: discoveredUSBTuners)
|
|
||||||
{
|
|
||||||
addUsbTuner(discoveredUSBTuner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists the USB devices. If listing fails with error -99, retries once with libusb warning logging turned on so
|
|
||||||
* that the log shows what caused it.
|
|
||||||
* @param usb context to list devices from
|
|
||||||
* @return devices, or null if they couldn't be listed
|
|
||||||
*/
|
|
||||||
private List<UsbDeviceInfo> getDeviceList(UsbContext usb)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return usb.devices();
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
if(e.code() != LIBUSB_ERROR_OTHER)
|
|
||||||
{
|
|
||||||
mLog.error("LibUsb - error during get device list - " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mLog.info("LibUsb reported error -99 during get device list - increasing logging level and retrying");
|
|
||||||
usb.setLogLevel(UsbLogLevel.WARNING);
|
|
||||||
List<UsbDeviceInfo> devices = usb.devices();
|
|
||||||
mLog.info("LibUsb - get device list count [" + devices.size() + "] during retry");
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
mLog.info("LibUsb - get device list reported error during retry - " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
usb.setLogLevel(UsbLogLevel.NONE);
|
DeviceList deviceList = new DeviceList();
|
||||||
|
int deviceCount = LibUsb.getDeviceList(mLibUsbApplicationContext, deviceList);
|
||||||
|
|
||||||
|
if(deviceCount >= 0)
|
||||||
|
{
|
||||||
|
mLog.info("LibUsb - discovered [" + deviceCount + "] potential usb devices");
|
||||||
|
|
||||||
|
for(Device device: deviceList)
|
||||||
|
{
|
||||||
|
int bus = LibUsb.getBusNumber(device);
|
||||||
|
int port = LibUsb.getPortNumber(device);
|
||||||
|
|
||||||
|
if(port > 0)
|
||||||
|
{
|
||||||
|
DeviceDescriptor deviceDescriptor = new DeviceDescriptor();
|
||||||
|
int status = LibUsb.getDeviceDescriptor(device, deviceDescriptor);
|
||||||
|
|
||||||
|
if(status == LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
TunerClass tunerClass = TunerClass.lookup(deviceDescriptor.idVendor(),
|
||||||
|
deviceDescriptor.idProduct());
|
||||||
|
|
||||||
|
String portAddress = getPortAddress(device);
|
||||||
|
|
||||||
|
if(tunerClass.isSupportedUsbTuner())
|
||||||
|
{
|
||||||
|
mLog.info("Discovered tuner at USB Bus [" + bus + "] Port [" + portAddress +
|
||||||
|
"] Tuner Class [" + tunerClass + "]");
|
||||||
|
ChannelizerType channelizerType = mUserPreferences.getTunerPreference().getChannelizerType();
|
||||||
|
DiscoveredUSBTuner discoveredUSBTuner = new DiscoveredUSBTuner(tunerClass, bus,
|
||||||
|
portAddress, channelizerType);
|
||||||
|
discoveredUSBTuners.add(discoveredUSBTuner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("LibUsb - unable to get device descriptor for device on bus [" + bus +
|
||||||
|
"] port [" + port + "] - status [" + status + "] - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Unref the device - it will be rediscovered under the device context when it is started
|
||||||
|
LibUsb.unrefDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
LibUsb.freeDeviceList(deviceList, false);
|
||||||
|
}
|
||||||
|
//If the get device list operation generated error -99, turn on warning logging and try device list
|
||||||
|
// again to see what's causing it.
|
||||||
|
else if(deviceCount == LibUsb.ERROR_OTHER)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mLog.info("LibUsb reported error -99 during get device list - increasing logging level and retrying");
|
||||||
|
LibUsb.setOption(mLibUsbApplicationContext, LibUsb.OPTION_LOG_LEVEL, LibUsb.LOG_LEVEL_WARNING);
|
||||||
|
deviceCount = LibUsb.getDeviceList(mLibUsbApplicationContext, deviceList);
|
||||||
|
if(deviceCount >=0)
|
||||||
|
{
|
||||||
|
mLog.info("LibUsb - get device list count [" + deviceCount + "] during retry");
|
||||||
|
LibUsb.freeDeviceList(deviceList, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("LibUsb - get device list reported error [" + deviceCount + "/" +
|
||||||
|
LibUsb.errorName(deviceCount) + "] during retry");
|
||||||
|
}
|
||||||
|
LibUsb.setOption(mLibUsbApplicationContext, LibUsb.OPTION_LOG_LEVEL, LibUsb.LOG_LEVEL_NONE);
|
||||||
|
mLog.info("LibUse - get device list reattempt complete - if additional logging was generated please notify developer");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.info("LibUsb error during get device list reattempt", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("LibUsb - error [" + deviceCount + "/" + LibUsb.errorName(deviceCount) + "] during get device list");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(UsbException e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
//Logging level only - nothing else to do
|
mLog.error("LibUsb - error during USB device discovery", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
mLog.info("LibUsb - get device list reattempt complete - if additional logging was generated please notify developer");
|
for(DiscoveredUSBTuner discoveredUSBTuner: discoveredUSBTuners)
|
||||||
|
{
|
||||||
|
addUsbTuner(discoveredUSBTuner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifies the USB tuner class of a device.
|
|
||||||
* @param device to identify
|
|
||||||
* @return tuner class, or null for a root hub or a device that isn't a supported tuner
|
|
||||||
*/
|
|
||||||
private static TunerClass getSupportedTunerClass(UsbDeviceInfo device)
|
|
||||||
{
|
|
||||||
//A root hub has no port numbers
|
|
||||||
if(device.portNumbers().isEmpty())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
TunerClass tunerClass = TunerClass.lookup((short)device.vendorId(), (short)device.productId());
|
|
||||||
return tunerClass.isSupportedUsbTuner() ? tunerClass : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a discovered tuner for a USB device.
|
|
||||||
* @param device to create a tuner for
|
|
||||||
* @return discovered tuner, or null if the device isn't a supported tuner
|
|
||||||
*/
|
|
||||||
private DiscoveredUSBTuner createDiscoveredTuner(UsbDeviceInfo device)
|
|
||||||
{
|
|
||||||
TunerClass tunerClass = getSupportedTunerClass(device);
|
|
||||||
|
|
||||||
if(tunerClass == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChannelizerType channelizerType = mUserPreferences.getTunerPreference().getChannelizerType();
|
|
||||||
return new DiscoveredUSBTuner(tunerClass, device.busNumber(), UsbPortAddress.of(device), channelizerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the USB device is a supported tuner and add if it has not already been added/discovered.
|
* Determines if the USB device is a supported tuner and add if it has not already been added/discovered.
|
||||||
* @param discoveredUSBTuner to add
|
* @param discoveredUSBTuner to add
|
||||||
@@ -664,104 +680,161 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* USB hotplug support to detect when USB tuners are added or removed. jlibusb reports changes as they happen
|
* Creates a dotted-notation string representing the port numbers for the device from the root hub through any
|
||||||
* where libusb supports hotplug (Linux and macOS) and polls the USB device list elsewhere (Windows).
|
* intermediate hubs.
|
||||||
*
|
* @param device to inspect
|
||||||
* Tuners already connected when detection starts are reported as arrivals too. Those were added during discovery
|
* @return formatted address
|
||||||
* at startup and are ignored here because the tuner model already has a tuner at that bus and port.
|
|
||||||
*/
|
*/
|
||||||
public class HotplugEventSupport implements UsbHotplugListener
|
public static String getPortAddress(Device device)
|
||||||
{
|
{
|
||||||
private UsbHotplug mUsbHotplug;
|
ByteBuffer portNumberArray = ByteBuffer.allocateDirect(7); //7 ports maximum.
|
||||||
|
int portCount = LibUsb.getPortNumbers(device, portNumberArray);
|
||||||
|
portNumberArray.rewind();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for(int x = 0; x < portCount; x++)
|
||||||
|
{
|
||||||
|
if(x > 0)
|
||||||
|
{
|
||||||
|
sb.append(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(portNumberArray.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* USB hotplug event listener to register, unregister and detect when USB devices are added or removed.
|
||||||
|
*
|
||||||
|
* Note: hotplug is not supported on all platforms.
|
||||||
|
*/
|
||||||
|
public class HotplugEventSupport implements HotplugCallback
|
||||||
|
{
|
||||||
|
private static final int HOTPLUG_CONTINUE_EVENT_SUPPORT = 0;
|
||||||
|
private HotplugCallbackHandle mHotplugCallbackHandle;
|
||||||
|
private ScheduledFuture<?> mEventProcessorFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts hotplug detection of supported USB tuners.
|
* LibUsb hotplug event notification
|
||||||
|
* @param context used for hotplug event registration
|
||||||
|
* @param device that arrived or left
|
||||||
|
* @param event value, arrived event or left event
|
||||||
|
* @param userData not used
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int processEvent(Context context, Device device, int event, Object userData)
|
||||||
|
{
|
||||||
|
int bus = LibUsb.getBusNumber(device);
|
||||||
|
int port = LibUsb.getPortNumber(device);
|
||||||
|
|
||||||
|
if(port > 0)
|
||||||
|
{
|
||||||
|
String portAddress = getPortAddress(device);
|
||||||
|
|
||||||
|
switch(event)
|
||||||
|
{
|
||||||
|
case LibUsb.HOTPLUG_EVENT_DEVICE_ARRIVED:
|
||||||
|
DeviceDescriptor deviceDescriptor = new DeviceDescriptor();
|
||||||
|
int status = LibUsb.getDeviceDescriptor(device, deviceDescriptor);
|
||||||
|
|
||||||
|
if(status == LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
TunerClass tunerClass = TunerClass.lookup(deviceDescriptor.idVendor(), deviceDescriptor.idProduct());
|
||||||
|
|
||||||
|
if(tunerClass.isSupportedUsbTuner())
|
||||||
|
{
|
||||||
|
mLog.info("Tuner plug-in detected at USB Bus [" + bus + "] Port [" + port +
|
||||||
|
"] Tuner Class [" + tunerClass + "]");
|
||||||
|
ChannelizerType channelizerType = mUserPreferences.getTunerPreference().getChannelizerType();
|
||||||
|
DiscoveredUSBTuner discoveredUSBTuner = new DiscoveredUSBTuner(tunerClass, bus,
|
||||||
|
portAddress, channelizerType);
|
||||||
|
|
||||||
|
if(tunerClass.isFuncubeTuner())
|
||||||
|
{
|
||||||
|
//Funcube tuners take a few moments to init the sound card interface. Delay adding
|
||||||
|
//the tuner so that it can be started correctly.
|
||||||
|
ThreadPool.SCHEDULED.schedule(() ->
|
||||||
|
{
|
||||||
|
addUsbTuner(discoveredUSBTuner);
|
||||||
|
}, 2, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addUsbTuner(discoveredUSBTuner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LibUsb.HOTPLUG_EVENT_DEVICE_LEFT:
|
||||||
|
DiscoveredTuner removed = removeUsbTuner(bus, portAddress);
|
||||||
|
|
||||||
|
if(removed != null)
|
||||||
|
{
|
||||||
|
mLog.info("Tuner Unplugged: " + removed.getId());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HOTPLUG_CONTINUE_EVENT_SUPPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers for hotplug event notifications, if the capability is supported on this platform and starts an
|
||||||
|
* event processing timer to process hotplug events.
|
||||||
*/
|
*/
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
try
|
if(LibUsb.hasCapability(LibUsb.CAP_HAS_HOTPLUG))
|
||||||
{
|
{
|
||||||
mUsbHotplug = UsbHotplug.start(device -> getSupportedTunerClass(device) != null, this);
|
mHotplugCallbackHandle = new HotplugCallbackHandle();
|
||||||
mLog.info("LibUsb - started USB hotplug detection (" +
|
int events = LibUsb.HOTPLUG_EVENT_DEVICE_ARRIVED | LibUsb.HOTPLUG_EVENT_DEVICE_LEFT;
|
||||||
(mUsbHotplug.mode() == UsbHotplug.Mode.NATIVE ? "native" : "polling") + ")");
|
int flags = LibUsb.HOTPLUG_ENUMERATE;
|
||||||
}
|
int vendorId = LibUsb.HOTPLUG_MATCH_ANY;
|
||||||
catch(UsbException | RuntimeException e)
|
int productId = LibUsb.HOTPLUG_MATCH_ANY;
|
||||||
{
|
int deviceClass = LibUsb.HOTPLUG_MATCH_ANY;
|
||||||
mLog.error("LibUsb - unable to start USB hotplug detection", e);
|
|
||||||
mUsbHotplug = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
mLog.info("LibUsb - starting HotPlug detection");
|
||||||
* Stops hotplug detection. Waits for an arrival or removal that is being processed to finish.
|
int status = LibUsb.hotplugRegisterCallback(mLibUsbApplicationContext, events, flags, vendorId,
|
||||||
*/
|
productId, deviceClass, this, "sdrtrunk hotplug support", mHotplugCallbackHandle);
|
||||||
public void stop()
|
|
||||||
{
|
|
||||||
if(mUsbHotplug != null)
|
|
||||||
{
|
|
||||||
mUsbHotplug.close();
|
|
||||||
mUsbHotplug = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if(status == LibUsb.SUCCESS)
|
||||||
* A supported USB tuner was connected. Called on the jlibusb hotplug dispatch thread.
|
|
||||||
* @param device that arrived
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void arrived(UsbDeviceInfo device)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DiscoveredUSBTuner discoveredUSBTuner = createDiscoveredTuner(device);
|
|
||||||
|
|
||||||
if(discoveredUSBTuner == null ||
|
|
||||||
mDiscoveredTunerModel.hasUsbTuner(discoveredUSBTuner.getBus(), discoveredUSBTuner.getPortAddress()))
|
|
||||||
{
|
{
|
||||||
return;
|
Runnable eventHandler = () -> LibUsb.handleEvents(mLibUsbApplicationContext);
|
||||||
}
|
mEventProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(eventHandler,
|
||||||
|
0, 1, TimeUnit.SECONDS);
|
||||||
mLog.info("Tuner plug-in detected at USB Bus [" + discoveredUSBTuner.getBus() + "] Port [" +
|
|
||||||
discoveredUSBTuner.getPortAddress() + "] Tuner Class [" + discoveredUSBTuner.getTunerClass() + "]");
|
|
||||||
|
|
||||||
if(discoveredUSBTuner.getTunerClass().isFuncubeTuner())
|
|
||||||
{
|
|
||||||
//Funcube tuners take a few moments to init the sound card interface. Delay adding
|
|
||||||
//the tuner so that it can be started correctly.
|
|
||||||
ThreadPool.SCHEDULED.schedule(() -> addUsbTuner(discoveredUSBTuner), 2, TimeUnit.SECONDS);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
addUsbTuner(discoveredUSBTuner);
|
mHotplugCallbackHandle = null;
|
||||||
|
mLog.info("LibUsb - unable to register device hotplug listener - " + LibUsb.errorName(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
else
|
||||||
{
|
{
|
||||||
mLog.error("Error adding USB tuner after plug-in at USB Bus [" + device.busNumber() + "] Port [" +
|
mLog.info("LibUsb Hotplug event notification Is Not Supported on this platform.");
|
||||||
UsbPortAddress.of(device) + "]", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A supported USB tuner was disconnected. Called on the jlibusb hotplug dispatch thread.
|
* Unregisters from hotplug event notifications and stops the event processing timer
|
||||||
* @param device that left
|
|
||||||
*/
|
*/
|
||||||
@Override
|
public void stop()
|
||||||
public void left(UsbDeviceInfo device)
|
|
||||||
{
|
{
|
||||||
try
|
if(mHotplugCallbackHandle != null)
|
||||||
{
|
{
|
||||||
DiscoveredTuner removed = removeUsbTuner(device.busNumber(), UsbPortAddress.of(device));
|
LibUsb.hotplugDeregisterCallback(mLibUsbApplicationContext, mHotplugCallbackHandle);
|
||||||
|
mHotplugCallbackHandle = null;
|
||||||
if(removed != null)
|
|
||||||
{
|
|
||||||
mLog.info("Tuner Unplugged: " + removed.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
|
||||||
|
if(mEventProcessorFuture != null)
|
||||||
{
|
{
|
||||||
mLog.error("Error removing USB tuner after unplug at USB Bus [" + device.busNumber() + "] Port [" +
|
mEventProcessorFuture.cancel(true);
|
||||||
UsbPortAddress.of(device) + "]", e);
|
mEventProcessorFuture = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.manager;
|
|
||||||
|
|
||||||
import io.github.dsheirer.libusb.LibUsb;
|
|
||||||
import io.github.dsheirer.libusb.UsbContext;
|
|
||||||
import io.github.dsheirer.libusb.UsbDeviceHandle;
|
|
||||||
import io.github.dsheirer.libusb.UsbDeviceInfo;
|
|
||||||
import io.github.dsheirer.libusb.UsbException;
|
|
||||||
import io.github.dsheirer.source.SourceException;
|
|
||||||
import java.util.List;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the USB device at a tuner's bus and port address and reads its USB identity through jlibusb.
|
|
||||||
*
|
|
||||||
* The tuner libraries open a device by serial number or by index rather than by bus and port, so a tuner controller
|
|
||||||
* uses this to identify the device that the tuner manager discovered at a bus and port before handing it to its
|
|
||||||
* library. Devices are closed again before returning so that the library can open them.
|
|
||||||
*/
|
|
||||||
public final class UsbDeviceLookup
|
|
||||||
{
|
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(UsbDeviceLookup.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* libusb error code when the user doesn't have permission to open the device (LIBUSB_ERROR_ACCESS)
|
|
||||||
*/
|
|
||||||
public static final int ERROR_ACCESS = -3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* libusb error code when another program or driver has claimed the interface (LIBUSB_ERROR_BUSY)
|
|
||||||
*/
|
|
||||||
public static final int ERROR_BUSY = -6;
|
|
||||||
|
|
||||||
private UsbDeviceLookup()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a libusb context. Close it when done.
|
|
||||||
* @throws SourceException if libusb can't be initialized
|
|
||||||
*/
|
|
||||||
public static UsbContext createContext() throws SourceException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return UsbContext.create();
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(e.code()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists the USB devices, in libusb device list order.
|
|
||||||
* @param usb context
|
|
||||||
* @throws SourceException if the device list isn't available
|
|
||||||
*/
|
|
||||||
public static List<UsbDeviceInfo> devices(UsbContext usb) throws SourceException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return usb.devices();
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(e.code()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the device is at the bus and port address.
|
|
||||||
* @param device to test
|
|
||||||
* @param bus number
|
|
||||||
* @param portAddress in dotted notation, such as 2.3
|
|
||||||
*/
|
|
||||||
public static boolean isAt(UsbDeviceInfo device, int bus, String portAddress)
|
|
||||||
{
|
|
||||||
return device.busNumber() == bus && !device.portNumbers().isEmpty() && portAddress != null &&
|
|
||||||
portAddress.equals(UsbPortAddress.of(device));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the device at the bus and port address.
|
|
||||||
* @param usb context
|
|
||||||
* @param bus number
|
|
||||||
* @param portAddress in dotted notation, such as 2.3
|
|
||||||
* @throws SourceException if no device is at that bus and port
|
|
||||||
*/
|
|
||||||
public static UsbDeviceInfo find(UsbContext usb, int bus, String portAddress) throws SourceException
|
|
||||||
{
|
|
||||||
for(UsbDeviceInfo device: devices(usb))
|
|
||||||
{
|
|
||||||
if(isAt(device, bus, portAddress))
|
|
||||||
{
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SourceException("Couldn't find USB device at bus [" + bus + "] port [" + portAddress + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the device at the bus and port address.
|
|
||||||
* @param bus number
|
|
||||||
* @param portAddress in dotted notation, such as 2.3
|
|
||||||
* @throws SourceException if no device is at that bus and port
|
|
||||||
*/
|
|
||||||
public static UsbDeviceInfo find(int bus, String portAddress) throws SourceException
|
|
||||||
{
|
|
||||||
try(UsbContext usb = createContext())
|
|
||||||
{
|
|
||||||
return find(usb, bus, portAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the device. Close the handle before the context.
|
|
||||||
* @param usb context
|
|
||||||
* @param device to open
|
|
||||||
* @throws SourceException if the device can't be opened, logging the likely cause
|
|
||||||
*/
|
|
||||||
public static UsbDeviceHandle open(UsbContext usb, UsbDeviceInfo device) throws SourceException
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return usb.open(device);
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
if(e.code() == ERROR_ACCESS)
|
|
||||||
{
|
|
||||||
mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) blacklist driver and/or check udev rules");
|
|
||||||
throw new SourceException("access denied - if using linux, blacklist the default driver and/or install udev rules");
|
|
||||||
}
|
|
||||||
|
|
||||||
mLog.error("Can't open USB tuner - check driver or Linux udev rules");
|
|
||||||
throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(e.code()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the device's USB serial string.
|
|
||||||
* @param usb context
|
|
||||||
* @param device to read
|
|
||||||
* @return serial, or null if the device doesn't have a serial string or it can't be read
|
|
||||||
* @throws SourceException if the device has a serial string but can't be opened
|
|
||||||
*/
|
|
||||||
public static String readSerialNumber(UsbContext usb, UsbDeviceInfo device) throws SourceException
|
|
||||||
{
|
|
||||||
if(device.serialNumberIndex() == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try(UsbDeviceHandle handle = open(usb, device))
|
|
||||||
{
|
|
||||||
return handle.readString(device.serialNumberIndex());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the USB serial string of the device at the bus and port address.
|
|
||||||
* @param bus number
|
|
||||||
* @param portAddress in dotted notation, such as 2.3
|
|
||||||
* @return serial, or null if the device doesn't have a serial string or it can't be read
|
|
||||||
* @throws SourceException if no device is at that bus and port, or it can't be opened
|
|
||||||
*/
|
|
||||||
public static String readSerialNumber(int bus, String portAddress) throws SourceException
|
|
||||||
{
|
|
||||||
try(UsbContext usb = createContext())
|
|
||||||
{
|
|
||||||
return readSerialNumber(usb, find(usb, bus, portAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.manager;
|
|
||||||
|
|
||||||
import io.github.dsheirer.libusb.UsbDeviceInfo;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats the USB port address of a device: the port numbers from the root hub through any intermediate hubs, in
|
|
||||||
* dotted notation such as 2.3. Together with the bus number it identifies the physical USB socket of a tuner.
|
|
||||||
*
|
|
||||||
* The tuner manager discovers tuners at this address and the tuner controllers find their device again by it, so it
|
|
||||||
* is formatted in one place.
|
|
||||||
*/
|
|
||||||
public final class UsbPortAddress
|
|
||||||
{
|
|
||||||
private UsbPortAddress()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Port address of a device.
|
|
||||||
* @param device to inspect
|
|
||||||
* @return formatted address, or an empty string for a root hub
|
|
||||||
*/
|
|
||||||
public static String of(UsbDeviceInfo device)
|
|
||||||
{
|
|
||||||
return format(device.portNumbers());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String format(List<Integer> ports)
|
|
||||||
{
|
|
||||||
return ports.stream().map(String::valueOf).collect(Collectors.joining("."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,10 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.source.tuner.rtl;
|
package io.github.dsheirer.source.tuner.rtl;
|
||||||
|
|
||||||
import io.github.dsheirer.libusb.UsbContext;
|
|
||||||
import io.github.dsheirer.libusb.UsbDeviceHandle;
|
|
||||||
import io.github.dsheirer.libusb.UsbDeviceInfo;
|
|
||||||
import io.github.dsheirer.libusb.UsbException;
|
|
||||||
import io.github.dsheirer.buffer.ByteNativeBufferFactory;
|
import io.github.dsheirer.buffer.ByteNativeBufferFactory;
|
||||||
import io.github.dsheirer.buffer.INativeBuffer;
|
import io.github.dsheirer.buffer.INativeBuffer;
|
||||||
import io.github.dsheirer.rtlsdr.DeviceInfo;
|
import io.github.dsheirer.rtlsdr.DeviceInfo;
|
||||||
@@ -36,7 +32,7 @@ import io.github.dsheirer.source.tuner.TunerClass;
|
|||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.UsbDeviceLookup;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.rtl.fc0012.FC0012TunerConfiguration;
|
import io.github.dsheirer.source.tuner.rtl.fc0012.FC0012TunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
|
import io.github.dsheirer.source.tuner.rtl.fc0013.FC0013TunerConfiguration;
|
||||||
@@ -48,6 +44,12 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.usb4java.Context;
|
||||||
|
import org.usb4java.Device;
|
||||||
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
import org.usb4java.DeviceHandle;
|
||||||
|
import org.usb4java.DeviceList;
|
||||||
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RTL-2832 tuner controller, implemented with the jRTL library (librtlsdr from rtl-sdr-blog).
|
* RTL-2832 tuner controller, implemented with the jRTL library (librtlsdr from rtl-sdr-blog).
|
||||||
@@ -326,40 +328,73 @@ public class RTL2832TunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
private UsbIdentity readUsbIdentity() throws SourceException
|
private UsbIdentity readUsbIdentity() throws SourceException
|
||||||
{
|
{
|
||||||
try(UsbContext usb = UsbDeviceLookup.createContext())
|
Context context = new Context();
|
||||||
|
int status = LibUsb.init(context);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
{
|
{
|
||||||
//RTL-2832 devices that precede this controller's device in the device list
|
throw new SourceException("Can't initialize libusb library - " + LibUsb.errorName(status));
|
||||||
List<UsbDeviceInfo> earlierDevices = new ArrayList<>();
|
}
|
||||||
|
|
||||||
for(UsbDeviceInfo device : UsbDeviceLookup.devices(usb))
|
try
|
||||||
|
{
|
||||||
|
DeviceList deviceList = new DeviceList();
|
||||||
|
int count = LibUsb.getDeviceList(context, deviceList);
|
||||||
|
|
||||||
|
if(count < 0)
|
||||||
{
|
{
|
||||||
if(TunerClass.lookup((short)device.vendorId(), (short)device.productId()) != TunerClass.RTL2832)
|
throw new SourceException("Can't get the USB device list - " + LibUsb.errorName(count));
|
||||||
{
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(UsbDeviceLookup.isAt(device, mBus, mPortAddress))
|
try
|
||||||
{
|
{
|
||||||
mUsbSerialNumberIndex = device.serialNumberIndex();
|
//RTL-2832 devices that precede this controller's device in the device list
|
||||||
String serial = readSerial(usb, device, true);
|
List<Device> earlierDevices = new ArrayList<>();
|
||||||
int serialPosition = 0;
|
List<DeviceDescriptor> earlierDescriptors = new ArrayList<>();
|
||||||
|
|
||||||
if(serial != null)
|
for(Device device : deviceList)
|
||||||
|
{
|
||||||
|
DeviceDescriptor descriptor = new DeviceDescriptor();
|
||||||
|
|
||||||
|
if(LibUsb.getDeviceDescriptor(device, descriptor) != LibUsb.SUCCESS ||
|
||||||
|
TunerClass.lookup(descriptor.idVendor(), descriptor.idProduct()) != TunerClass.RTL2832)
|
||||||
{
|
{
|
||||||
for(UsbDeviceInfo earlierDevice : earlierDevices)
|
continue;
|
||||||
{
|
|
||||||
if(serial.equals(readSerial(usb, earlierDevice, false)))
|
|
||||||
{
|
|
||||||
serialPosition++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new UsbIdentity(serial, serialPosition, earlierDevices.size());
|
if(LibUsb.getBusNumber(device) == mBus && LibUsb.getPortNumber(device) > 0 &&
|
||||||
}
|
mPortAddress != null && mPortAddress.equals(TunerManager.getPortAddress(device)))
|
||||||
|
{
|
||||||
|
mUsbSerialNumberIndex = 0xFF & descriptor.iSerialNumber();
|
||||||
|
String serial = readSerial(device, descriptor, true);
|
||||||
|
int serialPosition = 0;
|
||||||
|
|
||||||
earlierDevices.add(device);
|
if(serial != null)
|
||||||
|
{
|
||||||
|
for(int x = 0; x < earlierDevices.size(); x++)
|
||||||
|
{
|
||||||
|
if(serial.equals(readSerial(earlierDevices.get(x), earlierDescriptors.get(x), false)))
|
||||||
|
{
|
||||||
|
serialPosition++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new UsbIdentity(serial, serialPosition, earlierDevices.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
earlierDevices.add(device);
|
||||||
|
earlierDescriptors.add(descriptor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.freeDeviceList(deviceList, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.exit(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
throw new SourceException("Couldn't find USB device at bus [" + mBus + "] port [" + mPortAddress + "]");
|
||||||
@@ -367,32 +402,51 @@ public class RTL2832TunerController extends TunerController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the device's USB serial string.
|
* Reads the device's USB serial string.
|
||||||
* @param usb context
|
|
||||||
* @param device to read
|
* @param device to read
|
||||||
|
* @param descriptor for the device
|
||||||
* @param required true to throw an exception when the device can't be opened, false to return null
|
* @param required true to throw an exception when the device can't be opened, false to return null
|
||||||
* @return serial or null if the device doesn't have a serial string
|
* @return serial or null if the device doesn't have a serial string
|
||||||
* @throws SourceException if the device is required and it can't be opened.
|
* @throws SourceException if the device is required and it can't be opened.
|
||||||
*/
|
*/
|
||||||
private static String readSerial(UsbContext usb, UsbDeviceInfo device, boolean required) throws SourceException
|
private static String readSerial(Device device, DeviceDescriptor descriptor, boolean required) throws SourceException
|
||||||
{
|
{
|
||||||
if(!required)
|
if(descriptor.iSerialNumber() == 0)
|
||||||
{
|
{
|
||||||
if(device.serialNumberIndex() == 0)
|
return null;
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try(UsbDeviceHandle handle = usb.open(device))
|
|
||||||
{
|
|
||||||
return handle.readString(device.serialNumberIndex());
|
|
||||||
}
|
|
||||||
catch(UsbException e)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return UsbDeviceLookup.readSerialNumber(usb, device);
|
DeviceHandle handle = new DeviceHandle();
|
||||||
|
int status = LibUsb.open(device, handle);
|
||||||
|
|
||||||
|
if(status != LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
if(!required)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(status == LibUsb.ERROR_ACCESS)
|
||||||
|
{
|
||||||
|
mLog.error("Access to USB tuner denied - (windows) reinstall zadig driver or (linux) check udev rules");
|
||||||
|
throw new SourceException("access denied - if using linux, install udev rules");
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.error("Can't open USB tuner - check driver or Linux udev rules");
|
||||||
|
throw new SourceException("Can't open USB tuner - reinstall driver? - " + LibUsb.errorName(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return LibUsb.getStringDescriptor(handle, descriptor.iSerialNumber());
|
||||||
|
}
|
||||||
|
catch(RuntimeException e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LibUsb.close(handle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ public class DiscoveredTunerModel extends AbstractTableModel implements Listener
|
|||||||
{
|
{
|
||||||
DiscoveredTuner discoveredTuner = mDiscoveredTuners.stream()
|
DiscoveredTuner discoveredTuner = mDiscoveredTuners.stream()
|
||||||
.filter(tuner -> tuner instanceof DiscoveredUSBTuner usbTuner &&
|
.filter(tuner -> tuner instanceof DiscoveredUSBTuner usbTuner &&
|
||||||
usbTuner.isAt(bus, portAddress)).findFirst().orElse(null);
|
usbTuner.isAt(bus, portAddress)).findFirst().get();
|
||||||
|
|
||||||
if(discoveredTuner != null)
|
if(discoveredTuner != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# LimeSDR udev rules
|
|
||||||
# Grants access to the logged-in user (uaccess) and to the plugdev group.
|
|
||||||
|
|
||||||
# LimeSDR-USB
|
|
||||||
SUBSYSTEM=="usb", ATTR{idVendor}=="1d50", ATTR{idProduct}=="6108", MODE="0660", GROUP="plugdev", TAG+="uaccess"
|
|
||||||
# LimeSDR Mini and LimeSDR Mini 2.0
|
|
||||||
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="601f", MODE="0660", GROUP="plugdev", TAG+="uaccess"
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 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.dsp.c4fm;
|
|
||||||
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1SymbolProcessorC4FM;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JUnit tests for the sync correlation score in the C4FM symbol processor.
|
|
||||||
*/
|
|
||||||
public class C4FMSymbolProcessorScoreTest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* A lagging sync detect on the first symbol after a buffer reload, followed by a coarse timing search that steps
|
|
||||||
* all the way left, scores a candidate whose first sync symbol starts just before the start of the buffer.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void scoreBeforeBufferStart()
|
|
||||||
{
|
|
||||||
float samplesPerSymbol = 25000f / 4800f;
|
|
||||||
P25P1SymbolProcessorC4FM processor = new P25P1SymbolProcessorC4FM(null, null);
|
|
||||||
processor.setSamplesPerSymbol(samplesPerSymbol);
|
|
||||||
//Sync workspace, sized as in setSamplesPerSymbol(), plus a 1024 sample buffer
|
|
||||||
int syncWorkspaceLength = (int)Math.ceil((processor.mSyncDetector.getDibitLength() + 1) * samplesPerSymbol);
|
|
||||||
processor.mBuffer = new float[syncWorkspaceLength + 1024];
|
|
||||||
|
|
||||||
//Offset to the final sync symbol that puts the first sync symbol at buffer index -0.3
|
|
||||||
double offset = (processor.mSyncDetector.getDibitLength() - 1) * samplesPerSymbol - 0.3;
|
|
||||||
assertDoesNotThrow(() -> processor.score(offset, samplesPerSymbol));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 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.voice;
|
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for AMBE voice frame error detection.
|
|
||||||
*/
|
|
||||||
public class AMBEFrameValidatorTest
|
|
||||||
{
|
|
||||||
//Base station voice frame A burst from a recording where JMBE decodes all three voice frames with zero errors.
|
|
||||||
private static final String VOICE_BURST = "5A3AC4B9E881526173002A6BB9E881526755FD7DF75F7173002A6BB9E881526173002A6B";
|
|
||||||
//Burst bit indices of the first C0 bits (voice frame bits 0, 4, 8) and first C1 bit (voice frame bit 25) of
|
|
||||||
//frame 1, which starts at burst bit 24.
|
|
||||||
private static final int FRAME_1_C0_BIT_0 = 24;
|
|
||||||
private static final int FRAME_1_C0_BIT_1 = 28;
|
|
||||||
private static final int FRAME_1_C0_BIT_2 = 32;
|
|
||||||
private static final int FRAME_1_C1_BIT_0 = 49;
|
|
||||||
|
|
||||||
private static CorrectedBinaryMessage getBurst()
|
|
||||||
{
|
|
||||||
return CorrectedBinaryMessage.loadHex(VOICE_BURST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void errorFreeBurst()
|
|
||||||
{
|
|
||||||
CorrectedBinaryMessage burst = getBurst();
|
|
||||||
|
|
||||||
for(int frame = 0; frame < 3; frame++)
|
|
||||||
{
|
|
||||||
assertEquals(0, AMBEFrameValidator.getErrorCount(burst, frame), "Frame " + frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(AMBEFrameValidator.isValid(burst));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void errorsAreCounted()
|
|
||||||
{
|
|
||||||
CorrectedBinaryMessage burst = getBurst();
|
|
||||||
burst.flip(FRAME_1_C0_BIT_0);
|
|
||||||
assertEquals(1, AMBEFrameValidator.getErrorCount(burst, 0));
|
|
||||||
burst.flip(FRAME_1_C1_BIT_0);
|
|
||||||
assertEquals(2, AMBEFrameValidator.getErrorCount(burst, 0));
|
|
||||||
burst.flip(FRAME_1_C0_BIT_1);
|
|
||||||
assertEquals(3, AMBEFrameValidator.getErrorCount(burst, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void fourC0ErrorsAreDetected()
|
|
||||||
{
|
|
||||||
CorrectedBinaryMessage burst = getBurst();
|
|
||||||
burst.flip(FRAME_1_C0_BIT_0);
|
|
||||||
burst.flip(FRAME_1_C0_BIT_1);
|
|
||||||
burst.flip(FRAME_1_C0_BIT_2);
|
|
||||||
burst.flip(FRAME_1_C0_BIT_2 + 4);
|
|
||||||
assertTrue(AMBEFrameValidator.getErrorCount(burst, 0) > AMBEFrameValidator.MAX_ERRORS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void validWhenAnyFrameIsValid()
|
|
||||||
{
|
|
||||||
CorrectedBinaryMessage burst = getBurst();
|
|
||||||
|
|
||||||
//Corrupt the C0 vector of each frame in turn (4 bit errors), verifying a single valid frame is sufficient.
|
|
||||||
int[] frameOffsets = {24, 96, 216};
|
|
||||||
|
|
||||||
for(int frame = 0; frame < 3; frame++)
|
|
||||||
{
|
|
||||||
for(int bit = 0; bit < 16; bit += 4)
|
|
||||||
{
|
|
||||||
burst.flip(frameOffsets[frame] + bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(AMBEFrameValidator.getErrorCount(burst, frame) > AMBEFrameValidator.MAX_ERRORS);
|
|
||||||
assertEquals(frame < 2, AMBEFrameValidator.isValid(burst), "Corrupted frames: " + (frame + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void randomBurstsAreRarelyValid()
|
|
||||||
{
|
|
||||||
Random random = new Random(1234);
|
|
||||||
int valid = 0;
|
|
||||||
int trials = 20000;
|
|
||||||
|
|
||||||
for(int trial = 0; trial < trials; trial++)
|
|
||||||
{
|
|
||||||
CorrectedBinaryMessage burst = new CorrectedBinaryMessage(288);
|
|
||||||
|
|
||||||
for(int bit = 0; bit < 288; bit++)
|
|
||||||
{
|
|
||||||
if(random.nextBoolean())
|
|
||||||
{
|
|
||||||
burst.set(bit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(AMBEFrameValidator.isValid(burst))
|
|
||||||
{
|
|
||||||
valid++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Expected false valid rate is ~3 x 1.3e-4 (ie ~8 of 20,000)
|
|
||||||
assertTrue(valid < 30, "Random bursts detected as valid: " + valid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void voiceMessageValidity()
|
|
||||||
{
|
|
||||||
assertTrue(new VoiceAMessage(DMRSyncPattern.BASE_STATION_VOICE, getBurst(), null, 0, 1).isValid());
|
|
||||||
|
|
||||||
CorrectedBinaryMessage corrupted = getBurst();
|
|
||||||
int[] frameOffsets = {24, 96, 216};
|
|
||||||
|
|
||||||
for(int offset : frameOffsets)
|
|
||||||
{
|
|
||||||
for(int bit = 0; bit < 16; bit += 4)
|
|
||||||
{
|
|
||||||
corrupted.flip(offset + bit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertFalse(new VoiceAMessage(DMRSyncPattern.BASE_STATION_VOICE, corrupted, null, 0, 1).isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies the AMBE frames match the burst bits, specifically for frame 2 where bits 32-35 are sourced from burst
|
|
||||||
* bits 128-131 and bits 36-39 from burst bits 180-183, with burst bit 180 set (ie a negative signed byte).
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void ambeFramesMatchBurstBits()
|
|
||||||
{
|
|
||||||
CorrectedBinaryMessage burst = getBurst();
|
|
||||||
burst.set(180);
|
|
||||||
burst.clear(128, 132);
|
|
||||||
List<byte[]> frames = new VoiceAMessage(DMRSyncPattern.BASE_STATION_VOICE, burst, null, 0, 1).getAMBEFrames();
|
|
||||||
int[] frameOffsets = {24, 96, 216};
|
|
||||||
|
|
||||||
for(int frame = 0; frame < 3; frame++)
|
|
||||||
{
|
|
||||||
for(int bit = 0; bit < 72; bit++)
|
|
||||||
{
|
|
||||||
int burstIndex = frameOffsets[frame] + bit + ((frame == 1 && bit >= 36) ? 48 : 0);
|
|
||||||
boolean frameBit = (frames.get(frame)[bit / 8] & (0x80 >> (bit % 8))) != 0;
|
|
||||||
assertEquals(burst.get(burstIndex), frameBit, "Frame " + frame + " bit " + bit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.source.tuner.limesdr;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.github.dsheirer.dsp.filter.channelizer.ComplexPolyphaseChannelizerM2;
|
|
||||||
import io.github.dsheirer.limesdr.DeviceEntry;
|
|
||||||
import io.github.dsheirer.limesdr.LogLevel;
|
|
||||||
import io.github.dsheirer.limesdr.Range;
|
|
||||||
import io.github.dsheirer.limesdr.StreamConfig;
|
|
||||||
import io.github.dsheirer.source.SourceException;
|
|
||||||
import io.github.dsheirer.source.tuner.TunerClass;
|
|
||||||
import io.github.dsheirer.source.tuner.TunerFactory;
|
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
|
||||||
import java.util.List;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for the parts of the LimeSDR tuner that don't require a device.
|
|
||||||
*/
|
|
||||||
class LimeSdrTunerTest
|
|
||||||
{
|
|
||||||
private static final DeviceEntry USB_A = DeviceEntry.parse("LimeSDR-USB, media=USB3.0, addr=1d50:6108, serial=0009060B00462008");
|
|
||||||
private static final DeviceEntry USB_B = DeviceEntry.parse("LimeSDR-USB, media=USB3.0, addr=1d50:6108, serial=0009060B00471B22");
|
|
||||||
private static final DeviceEntry MINI = DeviceEntry.parse("LimeSDR-Mini, media=USB3.0, addr=0403:601f, serial=1D3AC9A5C7B4D2");
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void lookupFindsEachUsbProduct()
|
|
||||||
{
|
|
||||||
assertEquals(TunerClass.LIMESDR, TunerClass.lookup((short)0x1D50, (short)0x6108));
|
|
||||||
assertEquals(TunerClass.LIMESDR, TunerClass.lookup((short)0x0403, (short)0x601F));
|
|
||||||
assertTrue(TunerClass.LIMESDR.isSupportedUsbTuner());
|
|
||||||
assertTrue(TunerType.LIMESDR.isSupportedUsbTuner());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void scalesSamplesAndRemovesHalfBitOffset()
|
|
||||||
{
|
|
||||||
short[] samples = {2047, -2048, -1, 0};
|
|
||||||
|
|
||||||
assertArrayEquals(new float[]{2047.5f / 2048, -2047.5f / 2048, -0.5f / 2048, 0.5f / 2048},
|
|
||||||
LimeSdrTunerController.scale(samples));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sampleRatesAreLimitedByTheDevice()
|
|
||||||
{
|
|
||||||
//LimeSDR-USB on USB 3.0
|
|
||||||
assertEquals(List.of(LimeSdrSampleRate.values()), LimeSdrSampleRate.getRates(100_000, 61_440_000));
|
|
||||||
//LimeSDR Mini
|
|
||||||
assertEquals(LimeSdrSampleRate.RATE_30_72, LimeSdrSampleRate.getRates(100_000, 30_720_000).getLast());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void usb2LimitsTheSampleRate()
|
|
||||||
{
|
|
||||||
Range range = new Range(100_000, 61_440_000, 0);
|
|
||||||
|
|
||||||
assertEquals(61_440_000, LimeSdrTunerController.getMaximumSampleRate(range, "USB3.0"));
|
|
||||||
double usb2 = LimeSdrTunerController.getMaximumSampleRate(range, "USB2.0");
|
|
||||||
assertEquals(LimeSdrSampleRate.RATE_10_0, LimeSdrSampleRate.getRates(range.min(), usb2).getLast());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void savedRateAboveTheDeviceMaximumIsReduced()
|
|
||||||
{
|
|
||||||
List<LimeSdrSampleRate> rates = LimeSdrSampleRate.getRates(100_000, 30_720_000);
|
|
||||||
|
|
||||||
assertEquals(LimeSdrSampleRate.RATE_30_72, LimeSdrSampleRate.getClosest(rates, LimeSdrSampleRate.RATE_61_44.getRate()));
|
|
||||||
assertEquals(LimeSdrSampleRate.RATE_1_25, LimeSdrSampleRate.getClosest(rates, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void sampleRatesDivideIntoPolyphaseChannels()
|
|
||||||
{
|
|
||||||
for(LimeSdrSampleRate rate : LimeSdrSampleRate.values())
|
|
||||||
{
|
|
||||||
int channelCount = ComplexPolyphaseChannelizerM2.getChannelCount(rate.getRate());
|
|
||||||
assertEquals(0, channelCount % 2, rate.toString());
|
|
||||||
assertTrue((double)rate.getRate() / channelCount >= 25_000.0, rate.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void maximumUsbRateDoesNotOverflow()
|
|
||||||
{
|
|
||||||
assertTrue(LimeSdrSampleRate.RATE_61_44.getRate() * 24L <= Integer.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void antennaListExcludesNoneAndLoopbackPorts()
|
|
||||||
{
|
|
||||||
List<String> ports = List.of("None", "LNAH", "LNAL", "LNAW", "LB1", "LB2");
|
|
||||||
List<String> antennas = LimeSdrTunerController.getSelectableAntennas(ports);
|
|
||||||
|
|
||||||
assertEquals(List.of("LNAH", "LNAL", "LNAW"), antennas);
|
|
||||||
assertEquals("LNAW", LimeSdrTunerController.getDefaultAntenna(antennas));
|
|
||||||
assertEquals("LNAH", LimeSdrTunerController.getDefaultAntenna(List.of("LNAH")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deviceIsFoundBySerialNumber() throws SourceException
|
|
||||||
{
|
|
||||||
LimeSdrTunerController.UsbIdentity identity = new LimeSdrTunerController.UsbIdentity(0x1D50, 0x6108,
|
|
||||||
"0009060B00471B22");
|
|
||||||
|
|
||||||
assertEquals(USB_B, LimeSdrTunerController.findDevice(identity, List.of(MINI, USB_A, USB_B)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deviceWithoutSerialIsFoundWhenItIsTheOnlyOneOfItsKind() throws SourceException
|
|
||||||
{
|
|
||||||
LimeSdrTunerController.UsbIdentity mini = new LimeSdrTunerController.UsbIdentity(0x0403, 0x601F, null);
|
|
||||||
LimeSdrTunerController.UsbIdentity usb = new LimeSdrTunerController.UsbIdentity(0x1D50, 0x6108, null);
|
|
||||||
|
|
||||||
assertEquals(MINI, LimeSdrTunerController.findDevice(mini, List.of(USB_A, MINI)));
|
|
||||||
assertThrows(SourceException.class, () -> LimeSdrTunerController.findDevice(usb, List.of(USB_A, USB_B, MINI)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void deviceThatLimeSuiteCantSeeIsAnError()
|
|
||||||
{
|
|
||||||
LimeSdrTunerController.UsbIdentity identity = new LimeSdrTunerController.UsbIdentity(0x1D50, 0x6108,
|
|
||||||
"0009060B00462008");
|
|
||||||
|
|
||||||
assertThrows(SourceException.class, () -> LimeSdrTunerController.findDevice(identity, List.of(MINI)));
|
|
||||||
assertThrows(SourceException.class, () -> LimeSdrTunerController.findDevice(identity, List.of(USB_B)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void configurationSurvivesSerialization() throws Exception
|
|
||||||
{
|
|
||||||
LimeSdrTunerConfiguration configuration = new LimeSdrTunerConfiguration("test");
|
|
||||||
configuration.setSampleRate(LimeSdrSampleRate.RATE_5_0.getRate());
|
|
||||||
configuration.setGain(55);
|
|
||||||
configuration.setAntenna("LNAH");
|
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
|
||||||
String json = mapper.writeValueAsString(configuration);
|
|
||||||
TunerConfiguration restored = mapper.readValue(json, TunerConfiguration.class);
|
|
||||||
|
|
||||||
LimeSdrTunerConfiguration limeSdr = assertInstanceOf(LimeSdrTunerConfiguration.class, restored);
|
|
||||||
assertEquals(TunerType.LIMESDR, limeSdr.getTunerType());
|
|
||||||
assertEquals(LimeSdrSampleRate.RATE_5_0.getRate(), limeSdr.getSampleRate());
|
|
||||||
assertEquals(55, limeSdr.getGain());
|
|
||||||
assertEquals("LNAH", limeSdr.getAntenna());
|
|
||||||
assertEquals("test", limeSdr.getUniqueID());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void factoryCreatesTheConfiguration()
|
|
||||||
{
|
|
||||||
assertInstanceOf(LimeSdrTunerConfiguration.class, TunerFactory.getTunerConfiguration(TunerType.LIMESDR, "id"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void fifoHoldsAQuarterSecondOfSamples()
|
|
||||||
{
|
|
||||||
assertEquals(StreamConfig.DEFAULT_FIFO_SIZE, LimeSdrTunerController.getFifoSize(LimeSdrSampleRate.RATE_1_25));
|
|
||||||
assertEquals(10_000_000, LimeSdrTunerController.getFifoSize(LimeSdrSampleRate.RATE_40_0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void debugMessagesAreDiscarded()
|
|
||||||
{
|
|
||||||
LimeSdrTunerController.log(LogLevel.DEBUG, "Loss: pkt:4622555 exp: 000000017a863dc0, got: 000000017a864860");
|
|
||||||
LimeSdrTunerController.log(LogLevel.VERBOSE, "Set Rx LO frequency (450100180 Hz)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void droppedSamplesAreFoundFromTimestampGaps()
|
|
||||||
{
|
|
||||||
assertEquals(0, LimeSdrTunerController.getTimestampGap(-1, 5_000_000));
|
|
||||||
assertEquals(0, LimeSdrTunerController.getTimestampGap(65536, 65536));
|
|
||||||
assertEquals(718_080, LimeSdrTunerController.getTimestampGap(32_309_248, 33_027_328));
|
|
||||||
assertEquals(0, LimeSdrTunerController.getTimestampGap(32_309_248, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user