mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-11 16:17:24 -05:00
Compare commits
33 Commits
v0.5.0-alp
...
v0.5.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4f3db7436 | ||
|
|
3ebfa90ca1 | ||
|
|
733fdd0d7a | ||
|
|
09893605eb | ||
|
|
57468ef932 | ||
|
|
c6afb1c133 | ||
|
|
05c0b51fde | ||
|
|
de8cfa24f9 | ||
|
|
fe714c69a7 | ||
|
|
ac219cf2ba | ||
|
|
77b1102100 | ||
|
|
5117e6cbe8 | ||
|
|
61c4e13c3e | ||
|
|
f1190b7be3 | ||
|
|
49e7087b9b | ||
|
|
c397b5a601 | ||
|
|
c809dcc609 | ||
|
|
7eec82deee | ||
|
|
b061db1189 | ||
|
|
8715dd2d6b | ||
|
|
63b0aaaa7f | ||
|
|
d20dd1af8c | ||
|
|
e1f32d3d0b | ||
|
|
eaa3433034 | ||
|
|
838016c89f | ||
|
|
030a88f5a3 | ||
|
|
de3ba5529a | ||
|
|
7962758e05 | ||
|
|
0f9d1c0792 | ||
|
|
48be38a2d8 | ||
|
|
207893e5a8 | ||
|
|
e07d6fa854 | ||
|
|
e26698d0d8 |
2
.idea/dictionaries/denny.xml
generated
2
.idea/dictionaries/denny.xml
generated
@@ -4,6 +4,8 @@
|
||||
<w>bahroo</w>
|
||||
<w>broadcastify</w>
|
||||
<w>correlator</w>
|
||||
<w>deinterleave</w>
|
||||
<w>descrambled</w>
|
||||
<w>dinf</w>
|
||||
<w>esnid</w>
|
||||
<w>fleetsync</w>
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_13" default="false" project-jdk-name="13.0.2" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_13" default="false" project-jdk-name="15" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/modules.xml
generated
10
.idea/modules.xml
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk.iml" group="sdr-trunk" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_main.iml" group="sdr-trunk" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" filepath="$PROJECT_DIR$/.idea/modules/sdr-trunk_test.iml" group="sdr-trunk" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
118
build.gradle
118
build.gradle
@@ -24,7 +24,7 @@ plugins {
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.8'
|
||||
id 'org.beryx.runtime' version '1.8.1'
|
||||
id 'org.beryx.runtime' version '1.11.4'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -33,7 +33,7 @@ repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.5.0-alpha2'
|
||||
version = '0.5.0-alpha5'
|
||||
sourceCompatibility = '13'
|
||||
|
||||
sourceSets {
|
||||
@@ -65,7 +65,7 @@ dependencies {
|
||||
implementation 'com.miglayout:miglayout-swing:5.2'
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
implementation 'eu.hansolo:charts:1.0.5'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.6'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:15.1.8'
|
||||
implementation 'javax.usb:usb-api:1.0.2'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.4.0'
|
||||
implementation 'org.apache.commons:commons-compress:1.20'
|
||||
@@ -74,7 +74,7 @@ dependencies {
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'org.apache.mina:mina-core:2.1.3'
|
||||
implementation 'org.apache.mina:mina-http:2.1.3'
|
||||
implementation 'org.controlsfx:controlsfx:11.0.1'
|
||||
implementation 'org.controlsfx:controlsfx:11.0.2'
|
||||
implementation 'org.rauschig:jarchivelib:1.0.0'
|
||||
|
||||
implementation 'org.slf4j:slf4j-api:1.7.25'
|
||||
@@ -84,18 +84,18 @@ dependencies {
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
|
||||
//These must remain as compile options to ensure they are included in the build to produce multi-OS images
|
||||
compile "org.openjfx:javafx-base:13.0.2:win"
|
||||
compile "org.openjfx:javafx-base:13.0.2:linux"
|
||||
compile "org.openjfx:javafx-base:13.0.2:mac"
|
||||
compile "org.openjfx:javafx-controls:13.0.2:win"
|
||||
compile "org.openjfx:javafx-controls:13.0.2:linux"
|
||||
compile "org.openjfx:javafx-controls:13.0.2:mac"
|
||||
compile "org.openjfx:javafx-graphics:13.0.2:win"
|
||||
compile "org.openjfx:javafx-graphics:13.0.2:linux"
|
||||
compile "org.openjfx:javafx-graphics:13.0.2:mac"
|
||||
compile "org.openjfx:javafx-swing:13.0.2:win"
|
||||
compile "org.openjfx:javafx-swing:13.0.2:linux"
|
||||
compile "org.openjfx:javafx-swing:13.0.2:mac"
|
||||
compile "org.openjfx:javafx-base:15:win"
|
||||
compile "org.openjfx:javafx-base:15:linux"
|
||||
compile "org.openjfx:javafx-base:15:mac"
|
||||
compile "org.openjfx:javafx-controls:15:win"
|
||||
compile "org.openjfx:javafx-controls:15:linux"
|
||||
compile "org.openjfx:javafx-controls:15:mac"
|
||||
compile "org.openjfx:javafx-graphics:15:win"
|
||||
compile "org.openjfx:javafx-graphics:15:linux"
|
||||
compile "org.openjfx:javafx-graphics:15:mac"
|
||||
compile "org.openjfx:javafx-swing:15:win"
|
||||
compile "org.openjfx:javafx-swing:15:linux"
|
||||
compile "org.openjfx:javafx-swing:15:mac"
|
||||
}
|
||||
|
||||
application {
|
||||
@@ -110,6 +110,7 @@ idea {
|
||||
}
|
||||
|
||||
javafx {
|
||||
version = "15"
|
||||
modules = ['javafx.base', 'javafx.controls', 'javafx.graphics', 'javafx.swing']
|
||||
}
|
||||
|
||||
@@ -118,7 +119,6 @@ jar {
|
||||
attributes (
|
||||
'Implementation-Title' : 'sdrtrunk project',
|
||||
'Implementation-Version' : archiveVersion,
|
||||
'Class-Path' : 'jmbe-1.0.4.jar',
|
||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
||||
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
||||
@@ -133,48 +133,58 @@ jar {
|
||||
* tasks to produce platform-specific builds. If none of these paths exists, then an image will be created for the
|
||||
* host OS using the installed JDK.
|
||||
*/
|
||||
def jdk_linux_x86_64 = '/media/denny/WD250GB/java/bellsoft/linux-x64/jdk-14'
|
||||
def jdk_linux_x86_32 = '/media/denny/WD250GB/java/bellsoft/linux-x86/jdk-14'
|
||||
def jdk_osx_x86_64 = '/media/denny/WD250GB/java/bellsoft/osx-x64/jdk-14.jdk'
|
||||
def jdk_windows_x86_64 = '/media/denny/WD250GB/java/bellsoft/windows-x64/jdk-14'
|
||||
def jdk_windows_x86_32 = '/media/denny/WD250GB/java/bellsoft/windows-x86/jdk-14'
|
||||
def jdk_base = '/media/denny/WD250GB/java/bellsoft/'
|
||||
|
||||
def jdk_linux_arm64 = jdk_base + 'linux-arm64/jdk-15'
|
||||
def jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-15'
|
||||
def jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-15.jdk'
|
||||
def jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-15'
|
||||
|
||||
def file_jdk_linux_arm64 = new File(jdk_linux_arm64)
|
||||
def file_jdk_linux_x86_64 = new File(jdk_linux_x86_64)
|
||||
def file_jdk_linux_x86_32 = new File(jdk_linux_x86_32)
|
||||
def file_jdk_osx_x86_64 = new File(jdk_osx_x86_64)
|
||||
def file_jdk_windows_x86_64 = new File(jdk_windows_x86_64)
|
||||
def file_jdk_windows_x86_32 = new File(jdk_windows_x86_32)
|
||||
|
||||
def hasTargetJdk = file_jdk_linux_x86_32.exists() ||
|
||||
file_jdk_linux_x86_64.exists() ||
|
||||
def hasTargetJdk = file_jdk_linux_x86_64.exists() ||
|
||||
file_jdk_linux_arm64 ||
|
||||
file_jdk_osx_x86_64.exists() ||
|
||||
file_jdk_windows_x86_32.exists() ||
|
||||
file_jdk_windows_x86_64.exists()
|
||||
|
||||
runtime {
|
||||
if(file_jdk_linux_arm64.exists())
|
||||
{
|
||||
targetPlatform('linux-arm64-v' + version, jdk_linux_arm64)
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - Linux ARM 64-bit JDK was not found at " + jdk_linux_arm64)
|
||||
}
|
||||
|
||||
if(file_jdk_linux_x86_64.exists())
|
||||
{
|
||||
targetPlatform('linux-x86_64-v' + version, jdk_linux_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_linux_x86_32.exists())
|
||||
else
|
||||
{
|
||||
targetPlatform('linux-x86_32-v' + version, jdk_linux_x86_32)
|
||||
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_osx_x86_64.exists())
|
||||
{
|
||||
targetPlatform('osx-x86_64-v' + version, jdk_osx_x86_64)
|
||||
}
|
||||
else
|
||||
{
|
||||
println("Skipping OS Image - OSX x86 64-bit JDK was not found at " + jdk_osx_x86_64);
|
||||
}
|
||||
|
||||
if(file_jdk_windows_x86_64.exists())
|
||||
{
|
||||
targetPlatform('windows-x86_64-v' + version, jdk_windows_x86_64)
|
||||
}
|
||||
|
||||
if(file_jdk_windows_x86_32.exists())
|
||||
else
|
||||
{
|
||||
targetPlatform('windows-x86_32-v' + version, jdk_windows_x86_32)
|
||||
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
|
||||
}
|
||||
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
@@ -184,50 +194,18 @@ runtime {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for existence of JDK folders for each supported platform before creating runtime images
|
||||
* Copy JavaFX OS-specific libraries from each JDK to each platform target directory
|
||||
*/
|
||||
tasks.runtime.doFirst {
|
||||
|
||||
if(!file_jdk_linux_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - Linux x86 64-bit JDK was not found at " + jdk_linux_x86_64)
|
||||
}
|
||||
|
||||
if(!file_jdk_linux_x86_32.exists())
|
||||
{
|
||||
println("Skipping OS Image - Linux x86 32-bit JDK was not found at " + jdk_linux_x86_32)
|
||||
}
|
||||
|
||||
if(!file_jdk_osx_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - OSX x86 64-bit JDK was not found at " + jdk_osx_x86_64);
|
||||
}
|
||||
|
||||
if(!file_jdk_windows_x86_64.exists())
|
||||
{
|
||||
println("Skipping OS Image - Windows x86 64-bit JDK was not found at " + jdk_windows_x86_64)
|
||||
}
|
||||
|
||||
if(!file_jdk_windows_x86_32.exists())
|
||||
{
|
||||
println("Skipping OS Image - Windows x86 32-bit JDK was not found at " + jdk_windows_x86_32)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unused JavaFX OS-specific libraries from each platform target directory
|
||||
*/
|
||||
tasks.runtime.doLast {
|
||||
tasks.runtime.doLast
|
||||
{
|
||||
delete(fileTree('build/image/sdr-trunk-linux-arm64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(win|linux)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx.*-(linux|mac)\.jar/ })
|
||||
|
||||
//JavaFX web and media modules are transitive dependencies of ControlsFX but are not required for this application
|
||||
delete(fileTree('build/image/sdr-trunk-linux-arm64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-linux-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-osx-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_64-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
delete(fileTree('build/image/sdr-trunk-windows-x86_32-v' + version + '/lib').include { it.name ==~ /javafx-(web|media)-.*\.jar/ })
|
||||
}
|
||||
|
||||
@@ -93,12 +93,6 @@ public class AliasActionManager extends Module implements IMessageListener, List
|
||||
//No actions neeeded
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
//No actions neeeded
|
||||
}
|
||||
|
||||
/**
|
||||
* IMessageListener interface ... delegates to this class implementation of Listener<Message>
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,8 @@ public enum RadioFormat
|
||||
{
|
||||
APCO25("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||
"<html>APCO25 valid range is 0 to 16,777,215"),
|
||||
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||
"<html>DMR unit id valid range is 0 to 16,777,215"),
|
||||
PASSPORT("********", 0, 0x7FFFFF, "0 to 8,388,607",
|
||||
"<html>PASSPORT valid range is 0 to 8,388,607"),
|
||||
UNKNOWN("********", 1, 0xFFFFFF, "1 to 16,777,215",
|
||||
@@ -112,6 +114,8 @@ public enum RadioFormat
|
||||
{
|
||||
case APCO25:
|
||||
return APCO25;
|
||||
case DMR:
|
||||
return DMR;
|
||||
case PASSPORT:
|
||||
return PASSPORT;
|
||||
default:
|
||||
|
||||
@@ -25,6 +25,7 @@ package io.github.dsheirer.alias.id.radio;
|
||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
@@ -32,7 +33,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -46,6 +46,7 @@ public class RadioFormatter
|
||||
static
|
||||
{
|
||||
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,10 @@ import io.github.dsheirer.protocol.Protocol;
|
||||
*/
|
||||
public enum TalkgroupFormat
|
||||
{
|
||||
APCO25("********", 0, 0xFFFFFF, "0 to 65,535",
|
||||
APCO25("********", 0, 0xFFFF, "0 to 65,535",
|
||||
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
||||
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||
"<html>DMR talkgroup valid range is 0 to 16,777,215"),
|
||||
FLEETSYNC("###-####", 0, 0x7FFFFF, "001-0001 to 127-8192",
|
||||
"<html>Fleetsync valid ranges are 1-127(prefix)<br>and 1-8192(ident) (ie. 001-0001 to 127-8192)"),
|
||||
LTR("##-###", 0x101, 0x3FFF, "01-001 to 20-255",
|
||||
|
||||
@@ -23,6 +23,7 @@ package io.github.dsheirer.alias.id.talkgroup;
|
||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
||||
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
||||
@@ -35,7 +36,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -49,6 +49,7 @@ public class TalkgroupFormatter
|
||||
static
|
||||
{
|
||||
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
||||
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
|
||||
LTRTalkgroupFormatter ltr = new LTRTalkgroupFormatter();
|
||||
mFormatterMap.put(Protocol.LTR, ltr);
|
||||
|
||||
@@ -36,16 +36,17 @@ import org.slf4j.LoggerFactory;
|
||||
public abstract class AbstractAudioModule extends Module implements IAudioSegmentProvider, IdentifierUpdateListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
|
||||
private static final int DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH = 60 * 8000; // 1 minute @ 8kHz
|
||||
|
||||
public static final long DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH = 60 * 8000; // 1 minute @ 8kHz
|
||||
public static final int DEFAULT_TIMESLOT = 0;
|
||||
private final int mMaxSegmentAudioSampleLength;
|
||||
private Listener<AudioSegment> mAudioSegmentListener;
|
||||
protected MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||
protected MutableIdentifierCollection mIdentifierCollection;
|
||||
private Broadcaster<IdentifierUpdateNotification> mIdentifierUpdateNotificationBroadcaster = new Broadcaster<>();
|
||||
private AliasList mAliasList;
|
||||
private AudioSegment mAudioSegment;
|
||||
private int mAudioSampleCount = 0;
|
||||
private boolean mRecordAudioOverride;
|
||||
private int mTimeslot;
|
||||
|
||||
/**
|
||||
* Constructs an abstract audio module
|
||||
@@ -53,22 +54,30 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
* @param aliasList for aliasing identifiers
|
||||
* @param maxSegmentAudioSampleLength in milliseconds
|
||||
*/
|
||||
public AbstractAudioModule(AliasList aliasList, long maxSegmentAudioSampleLength)
|
||||
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentAudioSampleLength)
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
mMaxSegmentAudioSampleLength = (int)(maxSegmentAudioSampleLength * 8); //Convert milliseconds to samples
|
||||
mTimeslot = timeslot;
|
||||
mIdentifierCollection = new MutableIdentifierCollection(getTimeslot());
|
||||
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an abstract audio module
|
||||
* Constructs an abstract audio module with a default maximum audio segment length and a default timeslot 0.
|
||||
*/
|
||||
public AbstractAudioModule(AliasList aliasList)
|
||||
{
|
||||
this(aliasList, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
|
||||
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
|
||||
}
|
||||
|
||||
protected abstract int getTimeslot();
|
||||
/**
|
||||
* Timeslot for this audio module
|
||||
*/
|
||||
protected int getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current audio segment
|
||||
|
||||
@@ -83,9 +83,9 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
* @param aliasList for aliasing identifiers
|
||||
* @param maxAudioSegmentLength in milliseconds
|
||||
*/
|
||||
public AudioModule(AliasList aliasList, long maxAudioSegmentLength)
|
||||
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
|
||||
{
|
||||
super(aliasList, maxAudioSegmentLength);
|
||||
super(aliasList, timeslot, maxAudioSegmentLength);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,13 +102,6 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
removeAudioSegmentListener();
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
|
||||
@@ -231,80 +231,88 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||
*/
|
||||
private void process()
|
||||
{
|
||||
//Transfer in newly arrived audio segments
|
||||
mAudioSegmentQueue.drainTo(mAudioSegments);
|
||||
|
||||
//Remove any completed audio segments.
|
||||
mAudioSegments.removeIf(audioSegment -> {
|
||||
boolean complete = audioSegment.completeProperty().get();
|
||||
|
||||
if(complete)
|
||||
{
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
|
||||
return complete;
|
||||
});
|
||||
|
||||
//Only check for duplicates if there is more than one call
|
||||
if(mAudioSegments.size() > 1)
|
||||
try
|
||||
{
|
||||
List<AudioSegment> duplicates = new ArrayList<>();
|
||||
//Transfer in newly arrived audio segments
|
||||
mAudioSegmentQueue.drainTo(mAudioSegments);
|
||||
|
||||
int currentIndex = 0;
|
||||
while(currentIndex < mAudioSegments.size() - 1)
|
||||
{
|
||||
AudioSegment current = mAudioSegments.get(currentIndex);
|
||||
//Remove any completed audio segments.
|
||||
mAudioSegments.removeIf(audioSegment -> {
|
||||
boolean complete = audioSegment.completeProperty().get();
|
||||
|
||||
if(!current.isDuplicate())
|
||||
if(complete)
|
||||
{
|
||||
int checkIndex = currentIndex + 1;
|
||||
audioSegment.decrementConsumerCount();
|
||||
}
|
||||
|
||||
while(checkIndex < mAudioSegments.size())
|
||||
return complete;
|
||||
});
|
||||
|
||||
//Only check for duplicates if there is more than one call
|
||||
if(mAudioSegments.size() > 1)
|
||||
{
|
||||
List<AudioSegment> duplicates = new ArrayList<>();
|
||||
|
||||
int currentIndex = 0;
|
||||
while(currentIndex < mAudioSegments.size() - 1)
|
||||
{
|
||||
AudioSegment current = mAudioSegments.get(currentIndex);
|
||||
|
||||
if(!current.isDuplicate())
|
||||
{
|
||||
AudioSegment toCheck = mAudioSegments.get(checkIndex);
|
||||
int checkIndex = currentIndex + 1;
|
||||
|
||||
if(!toCheck.isDuplicate())
|
||||
while(checkIndex < mAudioSegments.size())
|
||||
{
|
||||
if(isDuplicate(current, toCheck))
|
||||
AudioSegment toCheck = mAudioSegments.get(checkIndex);
|
||||
|
||||
if(!toCheck.isDuplicate())
|
||||
{
|
||||
toCheck.setDuplicate(true);
|
||||
toCheck.decrementConsumerCount();
|
||||
duplicates.add(toCheck);
|
||||
if(isDuplicate(current, toCheck))
|
||||
{
|
||||
toCheck.setDuplicate(true);
|
||||
toCheck.decrementConsumerCount();
|
||||
duplicates.add(toCheck);
|
||||
}
|
||||
}
|
||||
|
||||
checkIndex++;
|
||||
}
|
||||
|
||||
checkIndex++;
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
mAudioSegments.removeAll(duplicates);
|
||||
}
|
||||
|
||||
mAudioSegments.removeAll(duplicates);
|
||||
}
|
||||
|
||||
//Finally, if the audio segment queue is empty, shutdown montitoring until a new segment arrives
|
||||
if(mAudioSegments.isEmpty())
|
||||
{
|
||||
//Block on the audio segment queue so that we can shutdown before any new segments are added, and
|
||||
//allow the add(segment) to restart monitoring as soon as needed.
|
||||
synchronized(mAudioSegmentQueue)
|
||||
//Finally, if the audio segment queue is empty, shutdown montitoring until a new segment arrives
|
||||
if(mAudioSegments.isEmpty())
|
||||
{
|
||||
if(mAudioSegmentQueue.isEmpty())
|
||||
//Block on the audio segment queue so that we can shutdown before any new segments are added, and
|
||||
//allow the add(segment) to restart monitoring as soon as needed.
|
||||
synchronized(mAudioSegmentQueue)
|
||||
{
|
||||
try
|
||||
if(mAudioSegmentQueue.isEmpty())
|
||||
{
|
||||
stopMonitoring();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Unexpected error during duplicate audio segment monitoring shutdown", e);
|
||||
//Do nothing, we got interrupted
|
||||
try
|
||||
{
|
||||
stopMonitoring();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Unexpected error during duplicate audio segment monitoring shutdown", e);
|
||||
//Do nothing, we got interrupted
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Unknown error while processing audio segments for duplicate call detection. Please report " +
|
||||
"this to the developer.", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +323,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
audioRecording.removePendingReplay();
|
||||
}
|
||||
}
|
||||
else if(urlResponse.startsWith("1 SKIPPED"))
|
||||
{
|
||||
//Broadcastify is telling us to skip audio upload - someone already uploaded it
|
||||
audioRecording.removePendingReplay();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Broadcastify calls API upload URL request failed [" + urlResponse + "]");
|
||||
|
||||
@@ -33,9 +33,9 @@ public abstract class AmbeAudioModule extends JmbeAudioModule
|
||||
private static final String AMBE_CODEC = "AMBE 3600 x 2450";
|
||||
private static boolean sLibraryStatusLogged = false;
|
||||
|
||||
public AmbeAudioModule(UserPreferences userPreferences, AliasList aliasList)
|
||||
public AmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
||||
{
|
||||
super(userPreferences, aliasList);
|
||||
super(userPreferences, aliasList, timeslot);
|
||||
|
||||
if(!sLibraryStatusLogged)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ public abstract class ImbeAudioModule extends JmbeAudioModule
|
||||
|
||||
public ImbeAudioModule(UserPreferences userPreferences, AliasList aliasList)
|
||||
{
|
||||
super(userPreferences, aliasList);
|
||||
super(userPreferences, aliasList, DEFAULT_TIMESLOT);
|
||||
|
||||
if(!sLibraryStatusLogged)
|
||||
{
|
||||
|
||||
@@ -54,14 +54,22 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
private IAudioCodec mAudioCodec;
|
||||
private UserPreferences mUserPreferences;
|
||||
|
||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList)
|
||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
||||
{
|
||||
super(aliasList);
|
||||
super(aliasList, timeslot, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
|
||||
mUserPreferences = userPreferences;
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
loadConverter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
super.dispose();
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
mAudioCodec = null;
|
||||
}
|
||||
|
||||
protected IAudioCodec getAudioCodec()
|
||||
{
|
||||
return mAudioCodec;
|
||||
@@ -236,10 +244,4 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
mAudioCodec = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
mAudioCodec = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +77,6 @@ public abstract class MBECallSequenceRecorder extends Module implements IMessage
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an MBE call sequence recording to the recording directory
|
||||
* @param sequence to write
|
||||
|
||||
@@ -129,7 +129,7 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
|
||||
public void dispose()
|
||||
{
|
||||
//Deregister from receiving preference update notifications
|
||||
MyEventBus.getEventBus().unregister(this);
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
|
||||
if(mAudioOutput != null)
|
||||
{
|
||||
@@ -141,7 +141,7 @@ public class AudioChannelPanel extends JPanel implements Listener<AudioEvent>, S
|
||||
private void init()
|
||||
{
|
||||
//Register to receive preference updates
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
setLayout(new MigLayout("align center center, insets 0 0 0 0",
|
||||
"[][][align right]0[grow,fill]", ""));
|
||||
|
||||
@@ -168,7 +168,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
updateToneInsertionAudioClips();
|
||||
|
||||
//Register to receive directory preference update notifications so we can update the preference items
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,6 +514,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
mCanProcessAudio = false;
|
||||
|
||||
if(mProcessorFuture != null)
|
||||
|
||||
@@ -183,7 +183,7 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.AUDIO_OUTPUT));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.AUDIO_OUTPUT));
|
||||
}
|
||||
});
|
||||
popup.add(outputMenu);
|
||||
|
||||
@@ -73,7 +73,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
public AudioPlaybackManager(UserPreferences userPreferences)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
MixerChannelConfiguration configuration = mUserPreferences.getPlaybackPreference().getMixerChannelConfiguration();
|
||||
|
||||
@@ -254,6 +254,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
if(mProcessingTask != null)
|
||||
{
|
||||
mProcessingTask.cancel(true);
|
||||
|
||||
@@ -834,6 +834,24 @@ public class BinaryMessage extends BitSet
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the twos-complement value of the bits between start and end, inclusive
|
||||
* @param start bit position
|
||||
* @param end bit position
|
||||
* @return twos complement value
|
||||
*/
|
||||
public int getTwosComplement(int start, int end)
|
||||
{
|
||||
BinaryMessage sub = getSubMessage(start, end);
|
||||
boolean negative = sub.get(start);
|
||||
sub.flip(0, sub.size());
|
||||
|
||||
int value = sub.getInt(0, sub.size()) + 1;
|
||||
value *= (negative ? -1 : 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the long value represented by the bit range. This method will
|
||||
* parse the bits in big endian or little endian format. The start value
|
||||
|
||||
@@ -33,6 +33,10 @@ public class SoftSyncDetector implements ISyncProcessor
|
||||
mThreshold = threshold;
|
||||
}
|
||||
|
||||
public SoftSyncDetector(long pattern, int threshold) {
|
||||
mPattern = pattern;
|
||||
mThreshold = threshold;
|
||||
}
|
||||
public void dispose()
|
||||
{
|
||||
mListener = null;
|
||||
|
||||
@@ -25,9 +25,13 @@ import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.text.DefaultCaret;
|
||||
import java.awt.*;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.metadata;
|
||||
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Channel and associated channel metadata
|
||||
*/
|
||||
public class ChannelAndMetadata
|
||||
{
|
||||
private Channel mChannel;
|
||||
private List<ChannelMetadata> mChannelMetadata;
|
||||
|
||||
/**
|
||||
* Connstructs an instance
|
||||
* @param channel
|
||||
* @param metadata
|
||||
*/
|
||||
public ChannelAndMetadata(Channel channel, List<ChannelMetadata> metadata)
|
||||
{
|
||||
mChannel = channel;
|
||||
mChannelMetadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel
|
||||
*/
|
||||
public Channel getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel Metadata
|
||||
*/
|
||||
public List<ChannelMetadata> getChannelMetadata()
|
||||
{
|
||||
return mChannelMetadata;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -57,10 +58,11 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
||||
|
||||
private List<ChannelMetadata> mChannelMetadata = new ArrayList();
|
||||
private Map<ChannelMetadata,Channel> mMetadataChannelMap = new HashMap();
|
||||
private Listener<ChannelAndMetadata> mChannelAddListener;
|
||||
|
||||
public ChannelMetadataModel()
|
||||
{
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,21 +84,66 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Collection<ChannelMetadata> channelMetadatas, Channel channel)
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup model row for the specified channel metadata
|
||||
*/
|
||||
public int getRow(ChannelMetadata channelMetadata)
|
||||
{
|
||||
if(mChannelMetadata.contains(channelMetadata))
|
||||
{
|
||||
return mChannelMetadata.indexOf(channelMetadata);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to be notified when channel metadata(s) are added to the model.
|
||||
* @param listener to receive channel associated with the channel metadata(s) that were added
|
||||
*/
|
||||
public void setChannelAddListener(Listener<ChannelAndMetadata> listener)
|
||||
{
|
||||
mChannelAddListener = listener;
|
||||
}
|
||||
|
||||
public void add(ChannelAndMetadata channelAndMetadata)
|
||||
{
|
||||
//Execute on the swing thread to avoid threading issues
|
||||
EventQueue.invokeLater(() -> {
|
||||
for(ChannelMetadata channelMetadata: channelMetadatas)
|
||||
for(ChannelMetadata channelMetadata: channelAndMetadata.getChannelMetadata())
|
||||
{
|
||||
mChannelMetadata.add(channelMetadata);
|
||||
mMetadataChannelMap.put(channelMetadata, channel);
|
||||
mMetadataChannelMap.put(channelMetadata, channelAndMetadata.getChannel());
|
||||
int index = mChannelMetadata.indexOf(channelMetadata);
|
||||
fireTableRowsInserted(index, index);
|
||||
channelMetadata.setUpdateEventListener(ChannelMetadataModel.this);
|
||||
}
|
||||
|
||||
if(mChannelAddListener != null)
|
||||
{
|
||||
mChannelAddListener.receive(channelAndMetadata);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the mapping of channel metadata to the channel used to create the channel metadatas (and processing chain)
|
||||
* @param channelMetadatas
|
||||
* @param channel
|
||||
*/
|
||||
public void updateChannelMetadataToChannelMap(Collection<ChannelMetadata> channelMetadatas, Channel channel)
|
||||
{
|
||||
for(ChannelMetadata channelMetadata: channelMetadatas)
|
||||
{
|
||||
mMetadataChannelMap.put(channelMetadata, channel);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(ChannelMetadata channelMetadata)
|
||||
{
|
||||
//Execute on the swing thread to avoid threading issues
|
||||
|
||||
@@ -25,7 +25,8 @@ import io.github.dsheirer.channel.state.State;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.ChannelModel;
|
||||
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||
import io.github.dsheirer.controller.channel.ChannelUtils;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||
@@ -38,6 +39,8 @@ import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
@@ -60,6 +63,8 @@ import java.util.Map;
|
||||
|
||||
public class ChannelMetadataPanel extends JPanel implements ListSelectionListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelMetadataPanel.class);
|
||||
|
||||
private static final String TABLE_PREFERENCE_KEY = "channel.metadata.panel";
|
||||
private ChannelModel mChannelModel;
|
||||
private ChannelProcessingManager mChannelProcessingManager;
|
||||
@@ -70,6 +75,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
private Map<State,Color> mBackgroundColors = new EnumMap<>(State.class);
|
||||
private Map<State,Color> mForegroundColors = new EnumMap<>(State.class);
|
||||
private JTableColumnWidthMonitor mTableColumnMonitor;
|
||||
private Channel mUserSelectedChannel;
|
||||
|
||||
/**
|
||||
* Table view for currently decoding channel metadata
|
||||
@@ -91,6 +97,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[grow,fill]") );
|
||||
|
||||
mTable = new JTable(mChannelProcessingManager.getChannelMetadataModel());
|
||||
mChannelProcessingManager.getChannelMetadataModel().setChannelAddListener(new ChannelAddListener());
|
||||
|
||||
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer)mTable.getDefaultRenderer(String.class);
|
||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
@@ -165,9 +172,10 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
|
||||
if(selectedMetadata != null)
|
||||
{
|
||||
Channel selectedChannel = mChannelProcessingManager.getChannelMetadataModel()
|
||||
mUserSelectedChannel = mChannelProcessingManager.getChannelMetadataModel()
|
||||
.getChannelFromMetadata(selectedMetadata);
|
||||
processingChain = mChannelProcessingManager.getProcessingChain(selectedChannel);
|
||||
|
||||
processingChain = mChannelProcessingManager.getProcessingChain(mUserSelectedChannel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,11 +413,12 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
|
||||
if(channel != null)
|
||||
{
|
||||
popupMenu.add(ChannelUtils.getContextMenu(mChannelModel, mChannelProcessingManager, channel, mTable));
|
||||
JMenuItem viewChannel = new JMenuItem("View/Edit: " + channel.getShortTitle());
|
||||
viewChannel.addActionListener(e2 -> MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel)));
|
||||
popupMenu.add(viewChannel);
|
||||
populated = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,4 +431,39 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to be notified when a channel and associated channel metadata(s) are added to the underlying
|
||||
* channel metadata model.
|
||||
*
|
||||
* When a channel is added, it is compared the the last user selected channel and if they are the same, it
|
||||
* invokes a selection event on the channel metadata row so that the channel metadata is re-selected. This is
|
||||
* primarily a hack to counter-act the DMR Capacity+ REST channel rotation where a channel is converted to a
|
||||
* traffic channel and the previous channel is restarted. The UI effect is that the user selected channel row
|
||||
* in the Now Playing window continually loses selection over the channel and causes the user to perpetually
|
||||
* chase the channel row.
|
||||
*/
|
||||
public class ChannelAddListener implements Listener<ChannelAndMetadata>
|
||||
{
|
||||
@Override
|
||||
public void receive(ChannelAndMetadata channelAndMetadata)
|
||||
{
|
||||
if(mUserSelectedChannel != null &&
|
||||
mUserSelectedChannel.getChannelID() == channelAndMetadata.getChannel().getChannelID())
|
||||
{
|
||||
List<ChannelMetadata> metadata = channelAndMetadata.getChannelMetadata();
|
||||
|
||||
if(metadata.size() > 0)
|
||||
{
|
||||
int modelRow = mChannelProcessingManager.getChannelMetadataModel().getRow(metadata.get(0));
|
||||
|
||||
if(modelRow >= 0)
|
||||
{
|
||||
int tableRow = mTable.convertRowIndexToView(modelRow);
|
||||
mTable.getSelectionModel().setSelectionInterval(tableRow, tableRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider,
|
||||
IDecoderStateEventProvider, ISourceEventProvider, IHeartbeatListener, ISquelchStateProvider,
|
||||
@@ -53,15 +53,35 @@ public abstract class AbstractChannelState extends Module implements IChannelEve
|
||||
protected Listener<IDecodeEvent> mDecodeEventListener;
|
||||
protected Listener<DecoderStateEvent> mDecoderStateListener;
|
||||
protected Listener<SourceEvent> mExternalSourceEventListener;
|
||||
protected Channel mChannel;
|
||||
private Channel mChannel;
|
||||
protected boolean mSourceOverflow = false;
|
||||
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channel configuration
|
||||
*/
|
||||
public AbstractChannelState(Channel channel)
|
||||
{
|
||||
mChannel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates/replaces the current channel configuration with the argument.
|
||||
*/
|
||||
protected void updateChannelConfiguration(Channel channel)
|
||||
{
|
||||
mChannel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel configuration for this channel state
|
||||
*/
|
||||
protected Channel getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked each time that a heartbeat is received so that sub-class implementations can check current timers and
|
||||
* adjust channel state as necessary. The heartbeat arrives on a periodic basis independent of any decoded
|
||||
@@ -69,7 +89,7 @@ public abstract class AbstractChannelState extends Module implements IChannelEve
|
||||
*/
|
||||
protected abstract void checkState();
|
||||
|
||||
public abstract Collection<ChannelMetadata> getChannelMetadata();
|
||||
public abstract List<ChannelMetadata> getChannelMetadata();
|
||||
|
||||
public abstract void updateChannelStateIdentifiers(IdentifierUpdateNotification notification);
|
||||
|
||||
|
||||
@@ -24,10 +24,8 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.string.SimpleStringIdentifier;
|
||||
import io.github.dsheirer.identifier.string.StringIdentifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -78,7 +76,7 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
|
||||
@Override
|
||||
public void receiveDecoderStateEvent(DecoderStateEvent event)
|
||||
{
|
||||
if(event.getEvent() == Event.RESET)
|
||||
if(event.getEvent() == Event.REQUEST_RESET)
|
||||
{
|
||||
getIdentifierCollection().update(mChannelNameIdentifier);
|
||||
}
|
||||
@@ -93,7 +91,7 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.ALWAYS_UNSQUELCH, State.IDLE));
|
||||
broadcast(new DecoderStateEvent(this, Event.REQUEST_ALWAYS_UNSQUELCH, State.IDLE));
|
||||
getIdentifierCollection().update(mChannelNameIdentifier);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public class ChangeChannelTimeoutEvent extends DecoderStateEvent
|
||||
*/
|
||||
public ChangeChannelTimeoutEvent(Object source, ChannelType channelType, long timeout, int timeslot)
|
||||
{
|
||||
super(source, Event.CHANGE_CALL_TIMEOUT, State.IDLE, timeslot);
|
||||
super(source, Event.REQUEST_CHANGE_CALL_TIMEOUT, State.IDLE, timeslot);
|
||||
mChannelType = channelType;
|
||||
mCallTimeoutMilliseconds = timeout;
|
||||
}
|
||||
|
||||
@@ -125,14 +125,6 @@ public abstract class DecoderState extends AbstractDecoderState
|
||||
*/
|
||||
public abstract void init();
|
||||
|
||||
/**
|
||||
* Disposes any resources or pointers held by this instance to prepare for
|
||||
* garbage collection
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for configuration identifiers and adds them to this decoder state's identifier collection.
|
||||
* @return
|
||||
|
||||
@@ -22,16 +22,12 @@
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
/**
|
||||
* State and Status request and notification events used for communication between Decoder States and Channel states
|
||||
* and by peripheral entities like the ChannelRotationMonitor.
|
||||
*/
|
||||
public class DecoderStateEvent
|
||||
{
|
||||
private static final DecoderStateEvent ACTIVE_STATE_TIMESLOT_0 =
|
||||
new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_ACTIVE_STATE, State.ACTIVE, 0);
|
||||
private static final DecoderStateEvent ACTIVE_STATE_TIMESLOT_1 =
|
||||
new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_ACTIVE_STATE, State.ACTIVE, 1);
|
||||
private static final DecoderStateEvent INACTIVE_STATE_TIMESLOT_0 =
|
||||
new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_INACTIVE_STATE, State.ACTIVE, 0);
|
||||
private static final DecoderStateEvent INACTIVE_STATE_TIMESLOT_1 =
|
||||
new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_INACTIVE_STATE, State.ACTIVE, 1);
|
||||
private Object mSource;
|
||||
private Event mEvent;
|
||||
private State mState;
|
||||
@@ -63,65 +59,14 @@ public class DecoderStateEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a channel state active event for the specified timeslot
|
||||
* @param timeslot that the state applies to
|
||||
* @return new event
|
||||
* Creates a decoder state notification event for a null source with the specified state and timeslot.
|
||||
* @param state (current) of the decoder
|
||||
* @param timeslot that is reporting state
|
||||
* @return new decoder state event
|
||||
*/
|
||||
public static DecoderStateEvent activeState(int timeslot)
|
||||
public static DecoderStateEvent stateNotification(State state, int timeslot)
|
||||
{
|
||||
if(timeslot == 0)
|
||||
{
|
||||
return ACTIVE_STATE_TIMESLOT_0;
|
||||
}
|
||||
else if(timeslot == 1)
|
||||
{
|
||||
return ACTIVE_STATE_TIMESLOT_1;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Only timeslots 0 and 1 are currently supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a channel state active event for the specified timeslot.
|
||||
* @param state currently
|
||||
* @return new event
|
||||
*/
|
||||
public static DecoderStateEvent activeState()
|
||||
{
|
||||
return activeState(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a channel state inactive event for the specified timeslot
|
||||
* @param timeslot that the state applies to
|
||||
* @return new event
|
||||
*/
|
||||
public static DecoderStateEvent inactiveState(int timeslot)
|
||||
{
|
||||
if(timeslot == 0)
|
||||
{
|
||||
return INACTIVE_STATE_TIMESLOT_0;
|
||||
}
|
||||
else if(timeslot == 1)
|
||||
{
|
||||
return INACTIVE_STATE_TIMESLOT_1;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException("Only timeslots 0 and 1 are currently supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a channel state active event for the specified timeslot.
|
||||
* @param state currently
|
||||
* @return new event
|
||||
*/
|
||||
public static DecoderStateEvent inactiveState()
|
||||
{
|
||||
return inactiveState(0);
|
||||
return new DecoderStateEvent(null, Event.NOTIFICATION_CHANNEL_STATE, state, timeslot);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
@@ -164,15 +109,23 @@ public class DecoderStateEvent
|
||||
|
||||
public enum Event
|
||||
{
|
||||
ALWAYS_UNSQUELCH,
|
||||
CHANGE_CALL_TIMEOUT,
|
||||
//Decode state discrete events
|
||||
CONTINUATION,
|
||||
DECODE,
|
||||
END,
|
||||
START,
|
||||
|
||||
NOTIFICATION_CHANNEL_STATE,
|
||||
NOTIFICATION_CHANNEL_ACTIVE_STATE,
|
||||
NOTIFICATION_CHANNEL_INACTIVE_STATE,
|
||||
RESET,
|
||||
SOURCE_FREQUENCY,
|
||||
START;
|
||||
NOTIFICATION_SOURCE_FREQUENCY,
|
||||
|
||||
|
||||
REQUEST_ALWAYS_UNSQUELCH,
|
||||
REQUEST_CHANGE_CALL_TIMEOUT,
|
||||
REQUEST_RESET,
|
||||
|
||||
//Request to convert the current channel to a traffic channel (used by DMR Cap+)
|
||||
REQUEST_CONVERT_TO_TRAFFIC_CHANNEL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Cache for frequently reused decoder state notification events and timeslots
|
||||
*/
|
||||
public class DecoderStateNotificationEventCache
|
||||
{
|
||||
private Map<Integer,Map<State,DecoderStateEvent>> mTimeslotMap = new HashMap<>();
|
||||
|
||||
public DecoderStateNotificationEventCache()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a decoder state notification event for the state and timeslot, or reuses a previously created event.
|
||||
* @param state for the decoder
|
||||
* @param timeslot of the decoder
|
||||
* @return state notification event
|
||||
*/
|
||||
public DecoderStateEvent getStateNotificationEvent(State state, int timeslot)
|
||||
{
|
||||
Map<State,DecoderStateEvent> timeslotStateMap = mTimeslotMap.get(timeslot);
|
||||
|
||||
if(timeslotStateMap == null)
|
||||
{
|
||||
timeslotStateMap = new HashMap<>();
|
||||
mTimeslotMap.put(timeslot, timeslotStateMap);
|
||||
}
|
||||
|
||||
DecoderStateEvent event = timeslotStateMap.get(state);
|
||||
|
||||
if(event == null)
|
||||
{
|
||||
event = DecoderStateEvent.stateNotification(state, timeslot);
|
||||
timeslotStateMap.put(state, event);
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,13 @@
|
||||
*/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
import io.github.dsheirer.controller.channel.ChannelConfigurationChangeNotification;
|
||||
import io.github.dsheirer.controller.channel.ChannelEvent;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||
@@ -39,7 +40,6 @@ import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentif
|
||||
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
@@ -50,11 +50,28 @@ import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Multi-Channel state tracks the overall state of all processing modules and decoders configured for the channel
|
||||
* and provides squelch control and decoder state reset events.
|
||||
*
|
||||
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
|
||||
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
|
||||
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
|
||||
* reset to IDLE.
|
||||
*
|
||||
* State Descriptions:
|
||||
* IDLE: Normal state. No voice or data call activity
|
||||
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
|
||||
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
|
||||
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
|
||||
* signal to the user that the call is ended, while continuing to display the call details for the user
|
||||
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
|
||||
*/
|
||||
public class MultiChannelState extends AbstractChannelState implements IDecoderStateEventListener, ISourceEventListener,
|
||||
IdentifierUpdateListener, IStateMachineListener
|
||||
{
|
||||
@@ -70,33 +87,23 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
private Map<Integer,MutableIdentifierCollection> mIdentifierCollectionMap = new TreeMap<>();
|
||||
private Map<Integer,StateMachine> mStateMachineMap = new TreeMap<>();
|
||||
private Map<Integer,StateMonitoringSquelchController> mSquelchControllerMap = new TreeMap<>();
|
||||
private int mTimeslotCount;
|
||||
private int[] mTimeslots;
|
||||
private DecoderStateNotificationEventCache mStateNotificationCache = new DecoderStateNotificationEventCache();
|
||||
private Listener<IdentifierUpdateNotification> mIdentifierUpdateListener = new IdentifierUpdateListenerProxy();
|
||||
|
||||
/**
|
||||
* Multi-Channel state tracks the overall state of all processing modules and decoders configured for the channel
|
||||
* and provides squelch control and decoder state reset events.
|
||||
*
|
||||
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
|
||||
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
|
||||
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
|
||||
* reset to IDLE.
|
||||
*
|
||||
* State Descriptions:
|
||||
* IDLE: Normal state. No voice or data call activity
|
||||
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
|
||||
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
|
||||
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
|
||||
* signal to the user that the call is ended, while continuing to display the call details for the user
|
||||
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
|
||||
* Constructs an instance
|
||||
* @param channel configuration
|
||||
* @param aliasModel for channel metadata and identifiers
|
||||
* @param timeslots array of timeslot numbers to use
|
||||
*/
|
||||
public MultiChannelState(Channel channel, AliasModel aliasModel, int timeslots)
|
||||
public MultiChannelState(Channel channel, AliasModel aliasModel, int[] timeslots)
|
||||
{
|
||||
super(channel);
|
||||
|
||||
mTimeslotCount = timeslots;
|
||||
mTimeslots = timeslots;
|
||||
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
for(int timeslot: timeslots)
|
||||
{
|
||||
mChannelMetadataMap.put(timeslot, new ChannelMetadata(aliasModel, timeslot));
|
||||
MutableIdentifierCollection mutableIdentifierCollection = new MutableIdentifierCollection(timeslot);
|
||||
@@ -113,36 +120,45 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
mSquelchControllerMap.put(timeslot, squelchController);
|
||||
stateMachine.addListener(squelchController);
|
||||
|
||||
stateMachine.setChannelType(mChannel.getChannelType());
|
||||
stateMachine.setIdentifierUpdateListener(mutableIdentifierCollection);
|
||||
stateMachine.setEndTimeoutBufferMilliseconds(RESET_TIMEOUT_DELAY);
|
||||
if(channel.getChannelType() == ChannelType.STANDARD)
|
||||
{
|
||||
stateMachine.setFadeTimeoutBufferMilliseconds(FADE_TIMEOUT_DELAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
stateMachine.setFadeTimeoutBufferMilliseconds(DecodeConfiguration.DEFAULT_CALL_TIMEOUT_DELAY_SECONDS * 1000);
|
||||
}
|
||||
stateMachine.setFadeTimeoutBufferMilliseconds(FADE_TIMEOUT_DELAY);
|
||||
}
|
||||
|
||||
configureChannelType(channel);
|
||||
|
||||
createConfigurationIdentifiers(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure items according to channel type
|
||||
* @param channel configuration
|
||||
*/
|
||||
private void configureChannelType(Channel channel)
|
||||
{
|
||||
for(int timeslot: mTimeslots)
|
||||
{
|
||||
StateMachine stateMachine = mStateMachineMap.get(timeslot);
|
||||
stateMachine.setChannelType(channel.getChannelType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive notification that the underlying channel configuration has changed.
|
||||
* @param notification
|
||||
*/
|
||||
@Subscribe
|
||||
public void channelConfigurationChanged(ChannelConfigurationChangeNotification notification)
|
||||
{
|
||||
updateChannelConfiguration(notification.getChannel());
|
||||
configureChannelType(notification.getChannel());
|
||||
createConfigurationIdentifiers(notification.getChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(State state, int timeslot)
|
||||
{
|
||||
//Broadcast current channel state so that channel rotation monitor can track
|
||||
if(State.MULTI_CHANNEL_ACTIVE_STATES.contains(state))
|
||||
{
|
||||
broadcast(DecoderStateEvent.activeState(timeslot));
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcast(DecoderStateEvent.inactiveState(timeslot));
|
||||
}
|
||||
|
||||
ChannelStateIdentifier stateIdentifier = ChannelStateIdentifier.create(state);
|
||||
ChannelStateIdentifier stateIdentifier = ChannelStateIdentifier.get(state);
|
||||
mIdentifierCollectionMap.get(timeslot).update(stateIdentifier);
|
||||
mChannelMetadataMap.get(timeslot).receive(new IdentifierUpdateNotification(stateIdentifier, IdentifierUpdateNotification.Operation.ADD, timeslot));
|
||||
|
||||
@@ -153,7 +169,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
mStateMachineMap.get(timeslot).setState(State.IDLE);
|
||||
break;
|
||||
case TEARDOWN:
|
||||
if(mChannel.isTrafficChannel())
|
||||
if(getChannel().isTrafficChannel())
|
||||
{
|
||||
checkTeardown();
|
||||
}
|
||||
@@ -191,9 +207,9 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
|
||||
if(teardown && !active)
|
||||
{
|
||||
if(mChannel.isTrafficChannel())
|
||||
if(getChannel().isTrafficChannel())
|
||||
{
|
||||
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
|
||||
broadcast(new ChannelEvent(getChannel(), ChannelEvent.Event.REQUEST_DISABLE));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -230,7 +246,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
*/
|
||||
private void createConfigurationIdentifiers(Channel channel)
|
||||
{
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
for(int timeslot: mTimeslots)
|
||||
{
|
||||
MutableIdentifierCollection identifierCollection = mIdentifierCollectionMap.get(timeslot);
|
||||
|
||||
@@ -334,9 +350,9 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
/**
|
||||
* Channel metadata for this channel.
|
||||
*/
|
||||
public Collection<ChannelMetadata> getChannelMetadata()
|
||||
public List<ChannelMetadata> getChannelMetadata()
|
||||
{
|
||||
return mChannelMetadataMap.values();
|
||||
return new ArrayList<>(mChannelMetadataMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,7 +361,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
for(int timeslot: mTimeslots)
|
||||
{
|
||||
reset(timeslot);
|
||||
}
|
||||
@@ -356,7 +372,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
private void reset(int timeslot)
|
||||
{
|
||||
mStateMachineMap.get(timeslot).setState(State.RESET);
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE, timeslot));
|
||||
broadcast(new DecoderStateEvent(this, Event.REQUEST_RESET, State.IDLE, timeslot));
|
||||
MutableIdentifierCollection identifierCollection = mIdentifierCollectionMap.get(timeslot);
|
||||
identifierCollection.remove(IdentifierClass.USER);
|
||||
}
|
||||
@@ -364,7 +380,7 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
for(int timeslot: mTimeslots)
|
||||
{
|
||||
mIdentifierCollectionMap.get(timeslot).broadcastIdentifiers();
|
||||
mStateMachineMap.get(timeslot).setState(State.RESET);
|
||||
@@ -380,12 +396,6 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mDecodeEventListener = null;
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
@@ -480,9 +490,9 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
//Rebroadcast source frequency change events for the decoder(s) to process
|
||||
long frequency = sourceEvent.getValue().longValue();
|
||||
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
for(int timeslot: mTimeslots)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY,
|
||||
broadcast(new DecoderStateEvent(this, Event.NOTIFICATION_SOURCE_FREQUENCY,
|
||||
mStateMachineMap.get(timeslot).getState(), frequency));
|
||||
|
||||
//Create a new frequency configuration identifier so that downstream consumers receive the change
|
||||
@@ -497,12 +507,13 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||
//Rebroadcast frequency error measurements to external listener if we're currently
|
||||
//in an active (ie sync locked) state.
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
for(int timeslot: mTimeslots)
|
||||
{
|
||||
if(State.MULTI_CHANNEL_ACTIVE_STATES.contains(mStateMachineMap.get(timeslot).getState()))
|
||||
{
|
||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
||||
mChannel.getChannelType().name()));
|
||||
getChannel().getChannelType().name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -522,10 +533,10 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case ALWAYS_UNSQUELCH:
|
||||
case REQUEST_ALWAYS_UNSQUELCH:
|
||||
mSquelchControllerMap.get(event.getTimeslot()).setSquelchLock(true);
|
||||
break;
|
||||
case CHANGE_CALL_TIMEOUT:
|
||||
case REQUEST_CHANGE_CALL_TIMEOUT:
|
||||
if(event instanceof ChangeChannelTimeoutEvent)
|
||||
{
|
||||
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
|
||||
@@ -538,19 +549,18 @@ public class MultiChannelState extends AbstractChannelState implements IDecoderS
|
||||
if(State.MULTI_CHANNEL_ACTIVE_STATES.contains(event.getState()))
|
||||
{
|
||||
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
||||
|
||||
//Broadcast current channel/timeslot state so that channel rotation monitor can track
|
||||
broadcast(mStateNotificationCache.getStateNotificationEvent(event.getState(), event.getTimeslot()));
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
||||
}
|
||||
else
|
||||
{
|
||||
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
||||
}
|
||||
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
||||
|
||||
//Broadcast current channel/timeslot state so that channel rotation monitor can track
|
||||
broadcast(mStateNotificationCache.getStateNotificationEvent(event.getState(), event.getTimeslot()));
|
||||
break;
|
||||
case RESET:
|
||||
case REQUEST_RESET:
|
||||
/* Channel State does not respond to reset events */
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -21,12 +21,13 @@
|
||||
*/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
import io.github.dsheirer.controller.channel.ChannelConfigurationChangeNotification;
|
||||
import io.github.dsheirer.controller.channel.ChannelEvent;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||
@@ -51,7 +52,6 @@ import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -97,6 +97,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
private ChannelMetadata mChannelMetadata;
|
||||
private StateMachine mStateMachine = new StateMachine(0, State.SINGLE_CHANNEL_ACTIVE_STATES);
|
||||
private StateMonitoringSquelchController mSquelchController = new StateMonitoringSquelchController(0);
|
||||
private DecoderStateNotificationEventCache mStateNotificationCache = new DecoderStateNotificationEventCache();
|
||||
|
||||
public SingleChannelState(Channel channel, AliasModel aliasModel)
|
||||
{
|
||||
@@ -107,10 +108,21 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
|
||||
mStateMachine.addListener(this);
|
||||
mStateMachine.addListener(mSquelchController);
|
||||
mStateMachine.setChannelType(mChannel.getChannelType());
|
||||
mStateMachine.setIdentifierUpdateListener(mIdentifierCollection);
|
||||
mStateMachine.setEndTimeoutBufferMilliseconds(RESET_TIMEOUT_DELAY);
|
||||
if(channel.getChannelType() == ChannelType.STANDARD)
|
||||
|
||||
configureChannelType(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure items according to channel type
|
||||
* @param channel configuration
|
||||
*/
|
||||
private void configureChannelType(Channel channel)
|
||||
{
|
||||
mStateMachine.setChannelType(getChannel().getChannelType());
|
||||
|
||||
if(channel.isStandardChannel())
|
||||
{
|
||||
mStateMachine.setFadeTimeoutBufferMilliseconds(FADE_TIMEOUT_DELAY);
|
||||
}
|
||||
@@ -125,36 +137,38 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive notification that the underlying channel configuration has changed.
|
||||
* @param notification
|
||||
*/
|
||||
@Subscribe
|
||||
public void channelConfigurationChanged(ChannelConfigurationChangeNotification notification)
|
||||
{
|
||||
updateChannelConfiguration(notification.getChannel());
|
||||
configureChannelType(notification.getChannel());
|
||||
createConfigurationIdentifiers(notification.getChannel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(State state, int timeslot)
|
||||
{
|
||||
//Broadcast current channel state so that channel rotation monitor can track
|
||||
if(State.SINGLE_CHANNEL_ACTIVE_STATES.contains(state))
|
||||
{
|
||||
broadcast(DecoderStateEvent.activeState(timeslot));
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcast(DecoderStateEvent.inactiveState(timeslot));
|
||||
}
|
||||
|
||||
ChannelStateIdentifier stateIdentifier = ChannelStateIdentifier.create(state);
|
||||
ChannelStateIdentifier stateIdentifier = ChannelStateIdentifier.get(state);
|
||||
mIdentifierCollection.update(stateIdentifier);
|
||||
mChannelMetadata.receive(new IdentifierUpdateNotification(stateIdentifier, IdentifierUpdateNotification.Operation.ADD, timeslot));
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case IDLE:
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
|
||||
broadcast(new DecoderStateEvent(this, Event.REQUEST_RESET, State.IDLE));
|
||||
break;
|
||||
case RESET:
|
||||
reset();
|
||||
mStateMachine.setState(State.IDLE);
|
||||
break;
|
||||
case TEARDOWN:
|
||||
if(mChannel.isTrafficChannel())
|
||||
if(getChannel().isTrafficChannel())
|
||||
{
|
||||
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
|
||||
broadcast(new ChannelEvent(getChannel(), ChannelEvent.Event.REQUEST_DISABLE));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -258,7 +272,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
/**
|
||||
* Channel metadata for this channel.
|
||||
*/
|
||||
public Collection<ChannelMetadata> getChannelMetadata()
|
||||
public List<ChannelMetadata> getChannelMetadata()
|
||||
{
|
||||
return Collections.singletonList(mChannelMetadata);
|
||||
}
|
||||
@@ -270,7 +284,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
public void reset()
|
||||
{
|
||||
mStateMachine.setState(State.RESET);
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
|
||||
broadcast(new DecoderStateEvent(this, Event.REQUEST_RESET, State.IDLE));
|
||||
mIdentifierCollection.remove(IdentifierClass.USER);
|
||||
sourceOverflow(false);
|
||||
}
|
||||
@@ -280,7 +294,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
{
|
||||
mIdentifierCollection.broadcastIdentifiers();
|
||||
|
||||
if(mChannel.getChannelType() == ChannelType.TRAFFIC)
|
||||
if(getChannel().isTrafficChannel())
|
||||
{
|
||||
mStateMachine.setState(State.ACTIVE);
|
||||
}
|
||||
@@ -292,12 +306,6 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
mSquelchController.setSquelchLock(false);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mDecodeEventListener = null;
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
@@ -372,7 +380,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Rebroadcast source frequency change events for the decoder(s) to process
|
||||
long frequency = sourceEvent.getValue().longValue();
|
||||
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, mStateMachine.getState(), frequency));
|
||||
broadcast(new DecoderStateEvent(this, Event.NOTIFICATION_SOURCE_FREQUENCY, mStateMachine.getState(), frequency));
|
||||
|
||||
//Create a new frequency configuration identifier so that downstream consumers receive the change
|
||||
//via channel metadata and audio packet updates - this is a silent add that is sent as a notification
|
||||
@@ -386,7 +394,7 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
if(State.SINGLE_CHANNEL_ACTIVE_STATES.contains(mStateMachine.getState()))
|
||||
{
|
||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
||||
mChannel.getChannelType().name()));
|
||||
getChannel().getChannelType().name()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -405,10 +413,10 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case ALWAYS_UNSQUELCH:
|
||||
case REQUEST_ALWAYS_UNSQUELCH:
|
||||
mSquelchController.setSquelchLock(true);
|
||||
break;
|
||||
case CHANGE_CALL_TIMEOUT:
|
||||
case REQUEST_CHANGE_CALL_TIMEOUT:
|
||||
if(event instanceof ChangeChannelTimeoutEvent)
|
||||
{
|
||||
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
|
||||
@@ -420,19 +428,28 @@ public class SingleChannelState extends AbstractChannelState implements IDecoder
|
||||
if(State.SINGLE_CHANNEL_ACTIVE_STATES.contains(event.getState()))
|
||||
{
|
||||
mStateMachine.setState(event.getState());
|
||||
|
||||
//Broadcast state so channel rotation manager can track
|
||||
broadcast(mStateNotificationCache.getStateNotificationEvent(event.getState(), event.getTimeslot()));
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
if(mChannel.isTrafficChannel())
|
||||
if(getChannel().isTrafficChannel())
|
||||
{
|
||||
mStateMachine.setState(State.TEARDOWN);
|
||||
|
||||
//Broadcast state so channel rotation manager can track
|
||||
broadcast(mStateNotificationCache.getStateNotificationEvent(State.TEARDOWN, event.getTimeslot()));
|
||||
}
|
||||
else
|
||||
{
|
||||
mStateMachine.setState(State.FADE);
|
||||
|
||||
//Broadcast state so channel rotation manager can track
|
||||
broadcast(mStateNotificationCache.getStateNotificationEvent(State.FADE, event.getTimeslot()));
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
case REQUEST_RESET:
|
||||
/* Channel State does not respond to reset events */
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -83,7 +83,7 @@ public class ControllerPanel extends JPanel
|
||||
{
|
||||
if(index == mSettingsTabIndex)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewPlaylistRequest());
|
||||
MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -126,8 +126,6 @@ public class Channel extends Configuration implements Listener<SourceEvent>
|
||||
mChannelID = UNIQUE_ID++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a (new) deep copy of this channel
|
||||
*/
|
||||
@@ -183,6 +181,70 @@ public class Channel extends Configuration implements Listener<SourceEvent>
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a short title containing the system, site and channel name where each value is constrained to
|
||||
* ten characters each.
|
||||
* @return short title
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getShortTitle()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(getSystem() != null)
|
||||
{
|
||||
if(getSystem().length() > 10)
|
||||
{
|
||||
sb.append(getSystem().substring(0, 10)).append("..");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(getSystem());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("No System");
|
||||
}
|
||||
|
||||
sb.append("/");
|
||||
|
||||
if(getSite() != null)
|
||||
{
|
||||
if(getSite().length() > 10)
|
||||
{
|
||||
sb.append(getSite().substring(0, 10)).append("..");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(getSite());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("No Site");
|
||||
}
|
||||
sb.append("/");
|
||||
|
||||
if(getName() != null)
|
||||
{
|
||||
if(getName().length() > 10)
|
||||
{
|
||||
sb.append(getName().substring(0, 10)).append("..");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(getName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("No Channel");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the frequencies property as the source configuration changes or is initialized.
|
||||
*/
|
||||
@@ -390,6 +452,8 @@ public class Channel extends Configuration implements Listener<SourceEvent>
|
||||
sb.append(hasSite() ? getSite() : "SITE");
|
||||
sb.append("_");
|
||||
sb.append(getName());
|
||||
sb.append("_");
|
||||
sb.append(getChannelID());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.controller.channel;
|
||||
|
||||
import io.github.dsheirer.module.ModuleEventBusMessage;
|
||||
|
||||
/**
|
||||
* Notification that a channel configuration has changed.
|
||||
*/
|
||||
public class ChannelConfigurationChangeNotification extends ModuleEventBusMessage
|
||||
{
|
||||
private Channel mChannel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channel current
|
||||
*/
|
||||
public ChannelConfigurationChangeNotification(Channel channel)
|
||||
{
|
||||
mChannel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* New channel configuration
|
||||
*/
|
||||
public Channel getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.controller.channel;
|
||||
|
||||
import io.github.dsheirer.module.ModuleEventBusMessage;
|
||||
|
||||
/**
|
||||
* Request to convert a currently processing standard channel type to a traffic channel type. This request will
|
||||
* normally be handled by the ChannelProcessingManager instance.
|
||||
*/
|
||||
public class ChannelConversionRequest extends ModuleEventBusMessage
|
||||
{
|
||||
private Channel mCurrentChannel;
|
||||
private Channel mTrafficChannel;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
* @param currentChannel that is actively processing
|
||||
* @param trafficChannel to convert to
|
||||
*/
|
||||
public ChannelConversionRequest(Channel currentChannel, Channel trafficChannel)
|
||||
{
|
||||
mCurrentChannel = currentChannel;
|
||||
mTrafficChannel = trafficChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel configuration for the channel that is currently processing
|
||||
*/
|
||||
public Channel getCurrentChannel()
|
||||
{
|
||||
return mCurrentChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel configuration for the traffic channel to convert to
|
||||
*/
|
||||
public Channel getTrafficChannel()
|
||||
{
|
||||
return mTrafficChannel;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This 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.controller.channel;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
|
||||
/**
|
||||
* Channel grant event with a channel descriptor that identifies the channel that is being created
|
||||
*/
|
||||
public class ChannelGrantEvent extends ChannelEvent
|
||||
{
|
||||
private IChannelDescriptor mChannelDescriptor;
|
||||
private IdentifierCollection mIdentifierCollection;
|
||||
|
||||
/**
|
||||
* Constructs a channel grant event
|
||||
* @param channel with setup/configuration details
|
||||
* @param event to convey for the channel
|
||||
* @param channelDescriptor for the channel that is to be created
|
||||
* @param identifierCollection containing identifiers to preload into the channel
|
||||
*/
|
||||
public ChannelGrantEvent(Channel channel, Event event, IChannelDescriptor channelDescriptor,
|
||||
IdentifierCollection identifierCollection)
|
||||
{
|
||||
super(channel, event);
|
||||
mChannelDescriptor = channelDescriptor;
|
||||
mIdentifierCollection = identifierCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel descriptor that contains uplink and downlink information
|
||||
*/
|
||||
public IChannelDescriptor getChannelDescriptor()
|
||||
{
|
||||
return mChannelDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier collection to use in preloading the channel state for the allocated channel
|
||||
*/
|
||||
public IdentifierCollection getIdentifierCollection()
|
||||
{
|
||||
return mIdentifierCollection;
|
||||
}
|
||||
}
|
||||
@@ -21,25 +21,24 @@
|
||||
*/
|
||||
package io.github.dsheirer.controller.channel;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.metadata.ChannelAndMetadata;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadataModel;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||
import io.github.dsheirer.controller.channel.event.PreloadDataContent;
|
||||
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.module.decode.DecoderFactory;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.MessageActivityModel;
|
||||
import io.github.dsheirer.module.log.EventLogManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.RecorderFactory;
|
||||
@@ -51,15 +50,18 @@ import io.github.dsheirer.source.SourceException;
|
||||
import io.github.dsheirer.source.SourceManager;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import javafx.application.Platform;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Channel processing manager handles all starting and stopping of channel decoding. A processing chain is created
|
||||
@@ -71,7 +73,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelProcessingManager.class);
|
||||
private static final String TUNER_UNAVAILABLE_DESCRIPTION = "TUNER UNAVAILABLE";
|
||||
private Map<Channel,ProcessingChain> mProcessingChains = new HashMap<>();
|
||||
private Map<Channel,ProcessingChain> mProcessingChains = new ConcurrentHashMap<>();
|
||||
|
||||
private List<Listener<AudioSegment>> mAudioSegmentListeners = new CopyOnWriteArrayList<>();
|
||||
private List<Listener<IDecodeEvent>> mDecodeEventListeners = new CopyOnWriteArrayList<>();
|
||||
@@ -83,6 +85,8 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
private SourceManager mSourceManager;
|
||||
private AliasModel mAliasModel;
|
||||
private UserPreferences mUserPreferences;
|
||||
private List<Long> mLoggedFrequencies = new ArrayList<>();
|
||||
private List<ScheduledFuture<?>> mDelayedChannelStartTasks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs the channel processing manager
|
||||
@@ -166,6 +170,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
*
|
||||
* @param event that requests either enable/start or disable/stop a channel.
|
||||
*/
|
||||
@Deprecated //TODO: convert all usage to start(ChannelStartProcessingRequest request) method
|
||||
@Override
|
||||
public synchronized void receive(ChannelEvent event)
|
||||
{
|
||||
@@ -178,22 +183,32 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
{
|
||||
try
|
||||
{
|
||||
startProcessing(event);
|
||||
startProcessing(new ChannelStartProcessingRequest(event.getChannel()));
|
||||
}
|
||||
catch(ChannelException ce)
|
||||
{
|
||||
if(channel.getSourceConfiguration() instanceof SourceConfigTuner)
|
||||
{
|
||||
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
|
||||
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequency +
|
||||
"] - " + ce.getMessage());
|
||||
|
||||
if(!mLoggedFrequencies.contains(frequency))
|
||||
{
|
||||
mLoggedFrequencies.add(frequency);
|
||||
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequency +
|
||||
"] - " + ce.getMessage());
|
||||
}
|
||||
}
|
||||
else if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency)
|
||||
{
|
||||
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel
|
||||
.getSourceConfiguration()).getFrequencies();
|
||||
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequencies +
|
||||
"] - " + ce.getMessage());
|
||||
|
||||
if(frequencies.size() > 0 && !mLoggedFrequencies.contains(frequencies.get(0)))
|
||||
{
|
||||
mLoggedFrequencies.add(frequencies.get(0));
|
||||
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequencies +
|
||||
"] - " + ce.getMessage());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -203,40 +218,16 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
}
|
||||
break;
|
||||
case REQUEST_DISABLE:
|
||||
if(channel.isProcessing())
|
||||
{
|
||||
try
|
||||
{
|
||||
switch(channel.getChannelType())
|
||||
{
|
||||
case STANDARD:
|
||||
stopProcessing(channel, true);
|
||||
break;
|
||||
case TRAFFIC:
|
||||
//Don't remove traffic channel processing chains
|
||||
//until explicitly deleted, so that we can reuse them
|
||||
stopProcessing(channel, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(ChannelException ce)
|
||||
{
|
||||
mLog.error("Error stopping channel [" + channel.getName() + "] - " + ce.getMessage());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_DELETE:
|
||||
if(channel.isProcessing())
|
||||
{
|
||||
try
|
||||
{
|
||||
stopProcessing(channel, true);
|
||||
stopProcessing(channel);
|
||||
}
|
||||
catch(ChannelException ce)
|
||||
{
|
||||
mLog.error("Error stopping deleted channel [" + channel.getName() + "] - " + ce.getMessage());
|
||||
mLog.error("Error stopping channel [" + channel.getName() + "] - " + ce.getMessage());
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -252,7 +243,33 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
*/
|
||||
public void start(Channel channel) throws ChannelException
|
||||
{
|
||||
startProcessing(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE));
|
||||
startProcessing(new ChannelStartProcessingRequest(channel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to start processing a channel received over the Guava event bus.
|
||||
*
|
||||
* Note: since this is received over the event bus, we handle any channel exceptions inside this method.
|
||||
*/
|
||||
@Subscribe
|
||||
public void startChannelRequest(ChannelStartProcessingRequest request)
|
||||
{
|
||||
if(!isProcessing(request.getChannel()))
|
||||
{
|
||||
try
|
||||
{
|
||||
startProcessing(request);
|
||||
}
|
||||
catch(ChannelException ce)
|
||||
{
|
||||
if(request.isPersistentAttempt())
|
||||
{
|
||||
ScheduledFuture<?> future = ThreadPool.SCHEDULED
|
||||
.schedule(new DelayedChannelStartTask(request), 500, TimeUnit.MILLISECONDS);
|
||||
mDelayedChannelStartTasks.add(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,24 +278,21 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
*/
|
||||
public void stop(Channel channel) throws ChannelException
|
||||
{
|
||||
stopProcessing(channel, !channel.isTrafficChannel());
|
||||
stopProcessing(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a channel/processing chain
|
||||
*
|
||||
* @param event that requested the channel start
|
||||
* Starts a channel processing
|
||||
* @param request containing channel and other details
|
||||
* @throws ChannelException if a source is not available for the channel
|
||||
*/
|
||||
private void startProcessing(ChannelEvent event) throws ChannelException
|
||||
private void startProcessing(ChannelStartProcessingRequest request) throws ChannelException
|
||||
{
|
||||
Channel channel = event.getChannel();
|
||||
Channel channel = request.getChannel();
|
||||
|
||||
ProcessingChain processingChain = mProcessingChains.get(channel);
|
||||
|
||||
//If we're already processing, ignore the request
|
||||
if(processingChain != null && processingChain.isProcessing())
|
||||
if(isProcessing(channel))
|
||||
{
|
||||
throw new ChannelException("Channel is already playing");
|
||||
return;
|
||||
}
|
||||
|
||||
//Ensure that we can get a source before we construct a new processing chain
|
||||
@@ -305,74 +319,90 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
throw new ChannelException("No Tuner Available");
|
||||
}
|
||||
|
||||
if(processingChain == null)
|
||||
ProcessingChain processingChain = new ProcessingChain(channel, mAliasModel);
|
||||
|
||||
//Certain decoders aggregate the decode events in the parent channel that also includes any events produced
|
||||
//by the traffic channels. Establish listener registration depending on if this channel is a traffic channel
|
||||
//and the request contains the parent event history, or if this is a parent channel and the request contains
|
||||
//the traffic channel event history.
|
||||
if(request.hasParentDecodeEventHistory())
|
||||
{
|
||||
processingChain = new ProcessingChain(channel, mAliasModel);
|
||||
mChannelEventBroadcaster.addListener(processingChain);
|
||||
|
||||
/* Register global listeners */
|
||||
for(Listener<AudioSegment> listener : mAudioSegmentListeners)
|
||||
{
|
||||
processingChain.addAudioSegmentListener(listener);
|
||||
}
|
||||
|
||||
for(Listener<IDecodeEvent> listener : mDecodeEventListeners)
|
||||
{
|
||||
processingChain.addDecodeEventListener(listener);
|
||||
}
|
||||
|
||||
//Add a listener to detect source error state that indicates the channel should be shutdown
|
||||
processingChain.addSourceEventListener(sourceEvent ->
|
||||
{
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_ERROR_STATE && sourceEvent.getSource() != null)
|
||||
{
|
||||
Channel toShutdown = null;
|
||||
|
||||
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChains.entrySet())
|
||||
{
|
||||
if(entry.getValue().hasSource(sourceEvent.getSource()))
|
||||
{
|
||||
toShutdown = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(toShutdown != null)
|
||||
{
|
||||
mLog.warn("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
|
||||
|
||||
try
|
||||
{
|
||||
stopProcessing(toShutdown, true);
|
||||
}
|
||||
catch(ChannelException ce)
|
||||
{
|
||||
mLog.error("Error stopping channel [" + channel.getName() + "] with source error - " +
|
||||
ce.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Register this manager to receive channel events from traffic channel manager modules within
|
||||
//the processing chain
|
||||
processingChain.addChannelEventListener(this);
|
||||
|
||||
//Register channel to receive frequency correction events to show in the spectral display (hack!)
|
||||
processingChain.addFrequencyChangeListener(channel);
|
||||
|
||||
/* Processing Modules */
|
||||
List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences);
|
||||
processingChain.addModules(modules);
|
||||
|
||||
/* Setup message activity model with filtering */
|
||||
FilterSet<IMessage> messageFilter = DecoderFactory.getMessageFilters(modules);
|
||||
MessageActivityModel messageModel = new MessageActivityModel(messageFilter);
|
||||
processingChain.setMessageActivityModel(messageModel);
|
||||
|
||||
processingChain.getDecodeEventHistory().addListener(request.getParentDecodeEventHistory());
|
||||
}
|
||||
else if(request.hasChildDecodeEventHistory())
|
||||
{
|
||||
request.getChildDecodeEventHistory().addListener(processingChain.getDecodeEventHistory());
|
||||
}
|
||||
|
||||
/* Setup event logging */
|
||||
//Register to receive event bus requests/notifications
|
||||
processingChain.getEventBus().register(ChannelProcessingManager.this);
|
||||
|
||||
mChannelEventBroadcaster.addListener(processingChain);
|
||||
|
||||
/* Register global listeners */
|
||||
for(Listener<AudioSegment> listener : mAudioSegmentListeners)
|
||||
{
|
||||
processingChain.addAudioSegmentListener(listener);
|
||||
}
|
||||
|
||||
for(Listener<IDecodeEvent> listener : mDecodeEventListeners)
|
||||
{
|
||||
processingChain.addDecodeEventListener(listener);
|
||||
}
|
||||
|
||||
//Add a listener to detect source error state that indicates the channel should be shutdown
|
||||
processingChain.addSourceEventListener(sourceEvent ->
|
||||
{
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_ERROR_STATE && sourceEvent.getSource() != null)
|
||||
{
|
||||
Channel toShutdown = null;
|
||||
|
||||
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChains.entrySet())
|
||||
{
|
||||
if(entry.getValue().hasSource(sourceEvent.getSource()))
|
||||
{
|
||||
toShutdown = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(toShutdown != null)
|
||||
{
|
||||
mLog.warn("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
|
||||
|
||||
try
|
||||
{
|
||||
stopProcessing(toShutdown);
|
||||
}
|
||||
catch(ChannelException ce)
|
||||
{
|
||||
mLog.error("Error stopping channel [" + channel.getName() + "] with source error - " +
|
||||
ce.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Register this manager to receive channel events from traffic channel manager modules within
|
||||
//the processing chain
|
||||
processingChain.addChannelEventListener(this);
|
||||
|
||||
//Register channel to receive frequency correction events to show in the spectral display (hack!)
|
||||
processingChain.addFrequencyChangeListener(channel);
|
||||
|
||||
/* Processing Modules */
|
||||
List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences,
|
||||
request.getTrafficChannelManager());
|
||||
processingChain.addModules(modules);
|
||||
|
||||
//Post preload data from the request to the event bus. Modules that can handle preload data will annotate
|
||||
//their processor method with @Subscribe to receive each specific preload data content class.
|
||||
for(PreloadDataContent preloadDataContent: request.getPreloadDataContents())
|
||||
{
|
||||
processingChain.getEventBus().post(preloadDataContent);
|
||||
}
|
||||
|
||||
//Setup event logging
|
||||
List<Module> loggers = mEventLogManager.getLoggers(channel);
|
||||
|
||||
if(!loggers.isEmpty())
|
||||
@@ -387,25 +417,22 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
processingChain.setSource(source);
|
||||
|
||||
//Inject the channel identifier for traffic channels and preload user identifiers
|
||||
if(channel.isTrafficChannel() && event instanceof ChannelGrantEvent)
|
||||
if(channel.isTrafficChannel())
|
||||
{
|
||||
ChannelGrantEvent channelGrantEvent = (ChannelGrantEvent)event;
|
||||
IChannelDescriptor channelDescriptor = channelGrantEvent.getChannelDescriptor();
|
||||
|
||||
IdentifierCollection identifierCollection = channelGrantEvent.getIdentifierCollection();
|
||||
|
||||
if(channelDescriptor != null)
|
||||
if(request.hasChannelDescriptor() && request.hasIdentifierCollection())
|
||||
{
|
||||
for(int timeslot = 0; timeslot < channelDescriptor.getTimeslotCount(); timeslot++)
|
||||
for(int timeslot = 0; timeslot < request.getChannelDescriptor().getTimeslotCount(); timeslot++)
|
||||
{
|
||||
DecoderLogicalChannelNameIdentifier identifier =
|
||||
DecoderLogicalChannelNameIdentifier.create(channelDescriptor.toString(), channelDescriptor.getProtocol());
|
||||
DecoderLogicalChannelNameIdentifier.create(request.getChannelDescriptor().toString(),
|
||||
request.getChannelDescriptor().getProtocol());
|
||||
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(identifier,
|
||||
IdentifierUpdateNotification.Operation.ADD, timeslot);
|
||||
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
|
||||
|
||||
//Inject scramble parameters
|
||||
for(Identifier scrambleParameters: identifierCollection.getIdentifiers(Form.SCRAMBLE_PARAMETERS))
|
||||
for(Identifier scrambleParameters: request.getIdentifierCollection()
|
||||
.getIdentifiers(Form.SCRAMBLE_PARAMETERS))
|
||||
{
|
||||
//Broadcast scramble parameters to both timeslots
|
||||
IdentifierUpdateNotification scrambleNotification = new IdentifierUpdateNotification(scrambleParameters,
|
||||
@@ -415,13 +442,13 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
}
|
||||
}
|
||||
|
||||
for(Identifier userIdentifier : identifierCollection.getIdentifiers(IdentifierClass.USER))
|
||||
for(Identifier userIdentifier : request.getIdentifierCollection().getIdentifiers(IdentifierClass.USER))
|
||||
{
|
||||
if(channelDescriptor.getTimeslotCount() > 1)
|
||||
if(request.getChannelDescriptor().getTimeslotCount() > 1)
|
||||
{
|
||||
//Only broadcast an identifier update for the timeslot specified in the originating collection
|
||||
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(userIdentifier,
|
||||
IdentifierUpdateNotification.Operation.ADD, channelGrantEvent.getIdentifierCollection().getTimeslot());
|
||||
IdentifierUpdateNotification.Operation.ADD, request.getIdentifierCollection().getTimeslot());
|
||||
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
|
||||
}
|
||||
else
|
||||
@@ -435,13 +462,13 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
}
|
||||
|
||||
mProcessingChains.put(channel, processingChain);
|
||||
processingChain.start();
|
||||
|
||||
//This has to be done on the FX event thread when the playlist editor is constructed
|
||||
Platform.runLater(() -> channel.setProcessing(true));
|
||||
|
||||
getChannelMetadataModel().add(processingChain.getChannelState().getChannelMetadata(), channel);
|
||||
|
||||
mProcessingChains.put(channel, processingChain);
|
||||
getChannelMetadataModel().add(new ChannelAndMetadata(channel, processingChain.getChannelState().getChannelMetadata()));
|
||||
|
||||
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_START));
|
||||
}
|
||||
@@ -450,16 +477,15 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
* Stops the channel/processing chain.
|
||||
*
|
||||
* @param channel to stop
|
||||
* @param remove set to true to remove the associated processing chain.
|
||||
*/
|
||||
private void stopProcessing(Channel channel, boolean remove) throws ChannelException
|
||||
private void stopProcessing(Channel channel) throws ChannelException
|
||||
{
|
||||
//This has to be done on the FX event thread when the playlist editor is constructed
|
||||
Platform.runLater(() -> channel.setProcessing(false));
|
||||
|
||||
if(mProcessingChains.containsKey(channel))
|
||||
{
|
||||
ProcessingChain processingChain = mProcessingChains.get(channel);
|
||||
ProcessingChain processingChain = mProcessingChains.remove(channel);
|
||||
|
||||
for(ChannelMetadata channelMetadata: processingChain.getChannelState().getChannelMetadata())
|
||||
{
|
||||
@@ -469,7 +495,6 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
processingChain.stop();
|
||||
|
||||
processingChain.removeEventLoggingModules();
|
||||
|
||||
processingChain.removeRecordingModules();
|
||||
|
||||
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
||||
@@ -477,13 +502,11 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
channel.resetFrequencyCorrection();
|
||||
|
||||
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
||||
mChannelEventBroadcaster.removeListener(processingChain);
|
||||
|
||||
if(remove)
|
||||
{
|
||||
mChannelEventBroadcaster.removeListener(processingChain);
|
||||
mProcessingChains.remove(channel);
|
||||
processingChain.dispose();
|
||||
}
|
||||
//Unregister for event bus requests and notifications
|
||||
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
|
||||
processingChain.dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -496,13 +519,21 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
*/
|
||||
public void shutdown()
|
||||
{
|
||||
List<ScheduledFuture<?>> delayedTasks = new ArrayList<>(mDelayedChannelStartTasks);
|
||||
|
||||
for(ScheduledFuture<?> delayedTask: delayedTasks)
|
||||
{
|
||||
delayedTask.cancel(true);
|
||||
mDelayedChannelStartTasks.remove(delayedTask);
|
||||
}
|
||||
|
||||
List<Channel> channelsToStop = new ArrayList<>(mProcessingChains.keySet());
|
||||
|
||||
for(Channel channel : channelsToStop)
|
||||
{
|
||||
try
|
||||
{
|
||||
stopProcessing(channel, true);
|
||||
stopProcessing(channel);
|
||||
}
|
||||
catch(ChannelException ce)
|
||||
{
|
||||
@@ -511,6 +542,41 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a request to convert a currently processing standard channel type to a traffic channel type.
|
||||
* @param request from the currently processing channel's processing chain event bus
|
||||
*/
|
||||
@Subscribe
|
||||
public void convertToTrafficChannel(ChannelConversionRequest request)
|
||||
{
|
||||
//Update the channel to processing chain map.
|
||||
ProcessingChain processingChain = mProcessingChains.remove(request.getCurrentChannel());
|
||||
|
||||
if(processingChain != null)
|
||||
{
|
||||
//Remove the traffic channel manager from this processing chain. Reuse or reinsertion of the traffic
|
||||
//channel manager to another processing chain is handled separately.
|
||||
processingChain.removeTrafficChannelManager();
|
||||
|
||||
//Update processing flag for each configuration.
|
||||
Platform.runLater(() -> {
|
||||
request.getCurrentChannel().setProcessing(false);
|
||||
request.getTrafficChannel().setProcessing(true);
|
||||
});
|
||||
|
||||
mProcessingChains.put(request.getTrafficChannel(), processingChain);
|
||||
mChannelMetadataModel.updateChannelMetadataToChannelMap(processingChain.getChannelState().getChannelMetadata(),
|
||||
request.getTrafficChannel());
|
||||
|
||||
//Post a change notification so that processing chain modules can reconfigure
|
||||
processingChain.channelConfigurationChanged(new ChannelConfigurationChangeNotification(request.getTrafficChannel()));
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Request to convert to traffic channel ignored - no processing chain was found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message listener that will be added to all channels to receive
|
||||
* any messages.
|
||||
@@ -560,4 +626,31 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
{
|
||||
mChannelEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to scheduling attempt to start a channel after previous attempts failed for lack of tuner channel
|
||||
*/
|
||||
public class DelayedChannelStartTask implements Runnable
|
||||
{
|
||||
private ChannelStartProcessingRequest mRequest;
|
||||
|
||||
public DelayedChannelStartTask(ChannelStartProcessingRequest request)
|
||||
{
|
||||
mRequest = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
mDelayedChannelStartTasks.remove(this);
|
||||
startChannelRequest(mRequest);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error executing persistent channel start task");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This 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.controller.channel;
|
||||
|
||||
import io.github.dsheirer.channel.state.DecoderState;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.module.decode.event.ActivitySummaryFrame;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JSeparator;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
public class ChannelUtils
|
||||
{
|
||||
/**
|
||||
* Creates a context menu for the channel argument
|
||||
*/
|
||||
public static JMenu getContextMenu(final ChannelModel channelModel,
|
||||
final ChannelProcessingManager channelProcessingManager,
|
||||
final Channel channel,
|
||||
final Component anchor)
|
||||
{
|
||||
if(channel != null)
|
||||
{
|
||||
JMenu menu = new JMenu("Channel: " + channel.getName());
|
||||
|
||||
if(channel.isProcessing())
|
||||
{
|
||||
JMenuItem disable = new JMenuItem("Disable");
|
||||
disable.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
channelProcessingManager.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_DISABLE));
|
||||
}
|
||||
});
|
||||
|
||||
menu.add(disable);
|
||||
|
||||
menu.add(new JSeparator());
|
||||
|
||||
JMenuItem actySummaryItem =
|
||||
new JMenuItem("Activity Summary");
|
||||
|
||||
actySummaryItem.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
ProcessingChain chain = channelProcessingManager.getProcessingChain(channel);
|
||||
|
||||
if(chain != null)
|
||||
{
|
||||
for(DecoderState decoderState : chain.getDecoderStates())
|
||||
{
|
||||
sb.append(decoderState.getActivitySummary());
|
||||
}
|
||||
}
|
||||
|
||||
new ActivitySummaryFrame(sb.toString(), anchor);
|
||||
}
|
||||
});
|
||||
|
||||
menu.add(actySummaryItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
JMenuItem enable = new JMenuItem("Enable");
|
||||
enable.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
channelProcessingManager.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE));
|
||||
}
|
||||
});
|
||||
|
||||
menu.add(enable);
|
||||
}
|
||||
|
||||
menu.add(new JSeparator());
|
||||
|
||||
JMenuItem deleteItem = new JMenuItem("Delete");
|
||||
deleteItem.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
int response = JOptionPane.showConfirmDialog(anchor,
|
||||
"Do you want to delete channel " + channel.getName() + "?",
|
||||
"Are you sure?", JOptionPane.YES_NO_CANCEL_OPTION);
|
||||
|
||||
if(response == JOptionPane.YES_OPTION)
|
||||
{
|
||||
channelModel.receive(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_DELETE));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
menu.add(deleteItem);
|
||||
|
||||
return menu;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.controller.channel.event;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.module.ModuleEventBusMessage;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventHistory;
|
||||
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request to start/enable processing for the specified channel
|
||||
*/
|
||||
public class ChannelStartProcessingRequest extends ModuleEventBusMessage
|
||||
{
|
||||
private final Channel mChannel;
|
||||
private final IChannelDescriptor mChannelDescriptor;
|
||||
private final IdentifierCollection mIdentifierCollection;
|
||||
private final TrafficChannelManager mTrafficChannelManager;
|
||||
private final List<PreloadDataContent<?>> mPreloadDataContents = new ArrayList<>();
|
||||
private DecodeEventHistory mParentDecodeEventHistory;
|
||||
private DecodeEventHistory mChildDecodeEventHistory;
|
||||
private boolean mPersistentAttempt;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channel to start
|
||||
* @param channelDescriptor that identifies which channel in a multi-channel config to start
|
||||
* @param identifierCollection to use for the new channel
|
||||
* @param trafficChannelManager (optional) to use for the new channel
|
||||
*/
|
||||
public ChannelStartProcessingRequest(Channel channel, IChannelDescriptor channelDescriptor,
|
||||
IdentifierCollection identifierCollection,
|
||||
TrafficChannelManager trafficChannelManager)
|
||||
{
|
||||
mChannel = channel;
|
||||
mChannelDescriptor = channelDescriptor;
|
||||
mIdentifierCollection = (identifierCollection != null ? identifierCollection : new IdentifierCollection());
|
||||
mTrafficChannelManager = trafficChannelManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channel to start
|
||||
* @param channelDescriptor that identifies which channel in a multi-channel config to start
|
||||
* @param identifierCollection to use for the new channel
|
||||
*/
|
||||
public ChannelStartProcessingRequest(Channel channel, IChannelDescriptor channelDescriptor,
|
||||
IdentifierCollection identifierCollection)
|
||||
{
|
||||
this(channel, channelDescriptor, identifierCollection, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channel to start processing
|
||||
* @param trafficChannelManager optional to use in the processing chain
|
||||
*/
|
||||
public ChannelStartProcessingRequest(Channel channel, TrafficChannelManager trafficChannelManager)
|
||||
{
|
||||
this(channel, null, null, trafficChannelManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param channel to start processing
|
||||
*/
|
||||
public ChannelStartProcessingRequest(Channel channel)
|
||||
{
|
||||
this(channel, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a flag to persistently attempt to start this channel. When set to true, the channel processing manager
|
||||
* will repeatedly try to (re)start this channel while the application continues to run.
|
||||
* @param persistentAttempt if this start request should be continuously attempted until successful.
|
||||
*/
|
||||
public void setPersistentAttempt(boolean persistentAttempt)
|
||||
{
|
||||
mPersistentAttempt = persistentAttempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this request should be persistently attempted until the channel processing is started.
|
||||
*/
|
||||
public boolean isPersistentAttempt()
|
||||
{
|
||||
return mPersistentAttempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel to start processing
|
||||
* @return channel to start
|
||||
*/
|
||||
public Channel getChannel()
|
||||
{
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional traffic channel manager to (re)use.
|
||||
* @return traffic channel manager or null
|
||||
*/
|
||||
public TrafficChannelManager getTrafficChannelManager()
|
||||
{
|
||||
return mTrafficChannelManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional parent decode event history module to be added as a decode event listener
|
||||
*/
|
||||
public DecodeEventHistory getParentDecodeEventHistory()
|
||||
{
|
||||
return mParentDecodeEventHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this request has a parent decode event history
|
||||
*/
|
||||
public boolean hasParentDecodeEventHistory()
|
||||
{
|
||||
return mParentDecodeEventHistory != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent decode event history
|
||||
* @param parentHistory
|
||||
*/
|
||||
public void setParentDecodeEventHistory(DecodeEventHistory parentHistory)
|
||||
{
|
||||
mParentDecodeEventHistory = parentHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional child decode event history module
|
||||
*/
|
||||
public DecodeEventHistory getChildDecodeEventHistory()
|
||||
{
|
||||
return mChildDecodeEventHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this request has a child decode event history
|
||||
*/
|
||||
public boolean hasChildDecodeEventHistory()
|
||||
{
|
||||
return mChildDecodeEventHistory != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the child decode event history
|
||||
* @param childHistory
|
||||
*/
|
||||
public void setChildDecodeEventHistory(DecodeEventHistory childHistory)
|
||||
{
|
||||
mChildDecodeEventHistory = childHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel descriptor to use for the started channel
|
||||
*/
|
||||
public IChannelDescriptor getChannelDescriptor()
|
||||
{
|
||||
return mChannelDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this request has a non-null channel descriptor
|
||||
*/
|
||||
public boolean hasChannelDescriptor()
|
||||
{
|
||||
return mChannelDescriptor != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier collection to use for preloading the channel with identifiers
|
||||
*/
|
||||
public IdentifierCollection getIdentifierCollection()
|
||||
{
|
||||
return mIdentifierCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this request has a non-null identifier collection
|
||||
*/
|
||||
public boolean hasIdentifierCollection()
|
||||
{
|
||||
return mIdentifierCollection != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data content to be preloaded onto the event bus at startup.
|
||||
* @param content to broadcast.
|
||||
*/
|
||||
public void addPreloadDataContent(PreloadDataContent<?> content)
|
||||
{
|
||||
mPreloadDataContents.add(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data content that should be broadcast on the processing chain event bus at startup.
|
||||
*/
|
||||
public List<PreloadDataContent<?>> getPreloadDataContents()
|
||||
{
|
||||
return mPreloadDataContents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.controller.channel.event;
|
||||
|
||||
import io.github.dsheirer.module.ModuleEventBusMessage;
|
||||
|
||||
/**
|
||||
* Data that should be preloaded into a processing chain by broadcasting the data onto the processing chain so that
|
||||
* any module can subscribe to it.
|
||||
* @param <T> data generic that should be loaded into modules in the processing chain.
|
||||
*/
|
||||
public abstract class PreloadDataContent<T> extends ModuleEventBusMessage
|
||||
{
|
||||
private final T mData;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param data to preload
|
||||
*/
|
||||
public PreloadDataContent(T data)
|
||||
{
|
||||
mData = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data to preload
|
||||
* @return
|
||||
*/
|
||||
public T getData()
|
||||
{
|
||||
return mData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the data payload is non-null
|
||||
*/
|
||||
public boolean hasData()
|
||||
{
|
||||
return mData != null;
|
||||
}
|
||||
}
|
||||
@@ -599,9 +599,9 @@ public class FilterFactory
|
||||
* @param alpha - roll-off factor.
|
||||
* @return - filter coefficients
|
||||
*/
|
||||
public static float[] getRootRaisedCosine(int samplesPerSymbol, int symbolCount, float alpha)
|
||||
public static float[] getRootRaisedCosine(double samplesPerSymbol, int symbolCount, float alpha)
|
||||
{
|
||||
int taps = samplesPerSymbol * symbolCount;
|
||||
int taps = (int)(samplesPerSymbol * symbolCount);
|
||||
|
||||
float scale = 0;
|
||||
|
||||
|
||||
@@ -165,34 +165,25 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
{
|
||||
PolyphaseChannelSource channelSource = null;
|
||||
|
||||
try
|
||||
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
|
||||
|
||||
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
|
||||
|
||||
if(outputProcessor != null)
|
||||
{
|
||||
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
|
||||
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
|
||||
|
||||
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
|
||||
|
||||
if(outputProcessor != null)
|
||||
try
|
||||
{
|
||||
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
|
||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
|
||||
|
||||
try
|
||||
{
|
||||
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
|
||||
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
|
||||
|
||||
mChannelSources.add(channelSource);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
|
||||
}
|
||||
mChannelSources.add(channelSource);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
|
||||
}
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" +
|
||||
mChannelCalculator.getCenterFrequency() + "] and sample rate [" +
|
||||
(mChannelCalculator.getChannelCount() * mChannelCalculator.getChannelBandwidth()) + "]");
|
||||
}
|
||||
|
||||
return channelSource;
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
|
||||
package io.github.dsheirer.dsp.filter.design;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
@@ -83,41 +83,52 @@ public class FilterViewer extends Application
|
||||
// .passBandRipple(0.01)
|
||||
// .build();
|
||||
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(50000.0)
|
||||
.passBandCutoff(6500)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.005)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandStart(7200)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
// .sampleRate(50000.0)
|
||||
// .passBandCutoff(6500)
|
||||
// .passBandAmplitude(1.0)
|
||||
// .passBandRipple(0.005)
|
||||
// .stopBandAmplitude(0.0)
|
||||
// .stopBandStart(7200)
|
||||
// .stopBandRipple(0.01)
|
||||
// .build();
|
||||
|
||||
float[] taps = null;
|
||||
|
||||
// try
|
||||
// {
|
||||
// taps = FilterFactory.getSincM2Synthesizer(12500.0, 2, 19);
|
||||
// taps = FilterFactory.getSincFilter(25000.0, 12500.0, 2, 19, Window.WindowType.BLACKMAN_HARRIS_7, true);
|
||||
// }
|
||||
// catch(FilterDesignException fde)
|
||||
// {
|
||||
// mLog.error("Error");
|
||||
// }
|
||||
boolean rrc = false;
|
||||
|
||||
try
|
||||
if(rrc)
|
||||
{
|
||||
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||
double sampleRate = 50000.0;
|
||||
int symbolRate = 4800;
|
||||
double samplesPerSymbol = sampleRate / symbolRate / 2;
|
||||
float alpha = 0.25f;
|
||||
int symbolCount = 20;
|
||||
|
||||
if(designer.isValid())
|
||||
taps = FilterFactory.getRootRaisedCosine(samplesPerSymbol, symbolCount, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(50000)
|
||||
.passBandCutoff(4500)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.1)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandStart(7000)
|
||||
.stopBandRipple(0.01)
|
||||
.build();
|
||||
|
||||
try
|
||||
{
|
||||
taps = designer.getImpulseResponse();
|
||||
taps = FilterFactory.getTaps(specification);//
|
||||
}
|
||||
catch(Exception fde) //FilterDesignException
|
||||
{
|
||||
mLog.error("Couldn't design low pass baseband filter for sample rate");
|
||||
}
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
mLog.error("Filter design error", fde);
|
||||
}
|
||||
|
||||
|
||||
if(taps == null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.github.dsheirer.dsp.filter.design;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class FilterViewerLauncher
|
||||
{
|
||||
private JFXPanel mJFXPanel;
|
||||
|
||||
public FilterViewerLauncher()
|
||||
{
|
||||
mJFXPanel = new JFXPanel();
|
||||
|
||||
Platform.runLater(() ->
|
||||
{
|
||||
FilterViewer filterViewer = new FilterViewer();
|
||||
Stage stage = new Stage();
|
||||
|
||||
try
|
||||
{
|
||||
filterViewer.start(stage);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
FilterViewerLauncher filterViewerLauncher = new FilterViewerLauncher();
|
||||
}
|
||||
}
|
||||
@@ -24,22 +24,32 @@ package io.github.dsheirer.dsp.symbol;
|
||||
|
||||
public enum Dibit
|
||||
{
|
||||
D01_PLUS_3(false, true, 1, 4),
|
||||
D00_PLUS_1(false, false, 0, 0),
|
||||
D10_MINUS_1(true, false, 2, 8),
|
||||
D11_MINUS_3(true, true, 3, 12);
|
||||
D01_PLUS_3(false, true, 1, 4, 1),
|
||||
D00_PLUS_1(false, false, 0, 0, 0),
|
||||
D10_MINUS_1(true, false, 2, 8, 2),
|
||||
D11_MINUS_3(true, true, 3, 12, 3);
|
||||
|
||||
private boolean mBit1;
|
||||
private boolean mBit2;
|
||||
private int mLowValue;
|
||||
private int mHighValue;
|
||||
private int mValue;
|
||||
|
||||
private Dibit(boolean bit1, boolean bit2, int lowValue, int highValue)
|
||||
private Dibit(boolean bit1, boolean bit2, int lowValue, int highValue, int value)
|
||||
{
|
||||
mBit1 = bit1;
|
||||
mBit2 = bit2;
|
||||
mLowValue = lowValue;
|
||||
mHighValue = highValue;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary value of the symbol
|
||||
*/
|
||||
public int getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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.dsp.symbol;
|
||||
|
||||
import io.github.dsheirer.bits.BitSetFullException;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Circular buffer for storing and accessing dibits.
|
||||
*/
|
||||
public class DibitDelayBuffer
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DibitDelayBuffer.class);
|
||||
|
||||
protected Dibit[] mBuffer;
|
||||
protected int mPointer;
|
||||
|
||||
/**
|
||||
* Constructs a dibit delay buffer of the specified length
|
||||
*/
|
||||
public DibitDelayBuffer(int length)
|
||||
{
|
||||
Validate.isTrue(length > 0, "Dibit buffer length must be greater than 0");
|
||||
mBuffer = new Dibit[length];
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the buffer and preloads the buffer with all zeros
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
for(int x = 0; x < mBuffer.length; x++)
|
||||
{
|
||||
mBuffer[x] = Dibit.D00_PLUS_1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of this circular buffer
|
||||
*/
|
||||
public int length()
|
||||
{
|
||||
return mBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dibit at the specified index
|
||||
* @param index to get
|
||||
* @return dibit at the specified index
|
||||
*/
|
||||
public Dibit get(int index)
|
||||
{
|
||||
if(index < 0 || index >= mBuffer.length)
|
||||
{
|
||||
throw new IllegalArgumentException("Index argument must be between 0 and buffer length [" + mBuffer.length + "]");
|
||||
}
|
||||
|
||||
return mBuffer[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dibit at the specified index
|
||||
* @param index of the dibit
|
||||
* @param dibit to load at the specified index
|
||||
*/
|
||||
public void set(int index, Dibit dibit)
|
||||
{
|
||||
if(index < 0 || index >= mBuffer.length)
|
||||
{
|
||||
throw new IllegalArgumentException("Index argument must be between 0 and buffer length [" + mBuffer.length + "]");
|
||||
}
|
||||
|
||||
mBuffer[index] = dibit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ordered view of the internal circular buffer contents.
|
||||
*/
|
||||
public Dibit[] getBuffer()
|
||||
{
|
||||
Dibit[] transferBuffer = new Dibit[mBuffer.length];
|
||||
|
||||
int transferBufferPointer = 0;
|
||||
int bufferPointer = mPointer;
|
||||
|
||||
while(transferBufferPointer < transferBuffer.length)
|
||||
{
|
||||
transferBuffer[transferBufferPointer++] = mBuffer[bufferPointer++];
|
||||
|
||||
if(bufferPointer >= mBuffer.length)
|
||||
{
|
||||
bufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return transferBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a view of the buffer from the starting index to the specified length. Note: if the requested
|
||||
* length exceeds the length of this buffer, the contents will be unwrapped from the circular buffer and repeated.
|
||||
* @param start of the view where 0 is the first element.
|
||||
* @param length of the dibit view
|
||||
* @return an array of dibits
|
||||
*/
|
||||
public Dibit[] getBuffer(int start, int length)
|
||||
{
|
||||
Dibit[] transferBuffer = new Dibit[length];
|
||||
|
||||
int transferBufferPointer = 0;
|
||||
int bufferPointer = (mPointer + start) % mBuffer.length;
|
||||
|
||||
while(transferBufferPointer < transferBuffer.length)
|
||||
{
|
||||
transferBuffer[transferBufferPointer++] = mBuffer[bufferPointer++];
|
||||
|
||||
if(bufferPointer >= mBuffer.length)
|
||||
{
|
||||
bufferPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return transferBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a corrected binary message from the dibit buffer
|
||||
*
|
||||
* @param start dibit index where 0 is the oldest dibit and the newest dibit is buffer length - 1
|
||||
* @param dibitLength number of dibits to include in the message, making the message length = dibitLength * 2
|
||||
* @return corrected binary message
|
||||
*/
|
||||
public CorrectedBinaryMessage getMessage(int start, int dibitLength)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(dibitLength * 2);
|
||||
|
||||
int dibitCount = 0;
|
||||
int bufferPointer = (mPointer + start) % mBuffer.length;
|
||||
|
||||
try
|
||||
{
|
||||
while(dibitCount < dibitLength)
|
||||
{
|
||||
Dibit dibit = mBuffer[bufferPointer++];
|
||||
message.add(dibit.getBit1());
|
||||
message.add(dibit.getBit2());
|
||||
dibitCount++;
|
||||
|
||||
if(bufferPointer >= mBuffer.length)
|
||||
{
|
||||
bufferPointer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(BitSetFullException e)
|
||||
{
|
||||
//This shouldn't happen
|
||||
mLog.error("Bit set full ??", e);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to view the buffer contents with the dibits decoded as an array of zeros and ones.
|
||||
*/
|
||||
public int[] getBufferAsArray()
|
||||
{
|
||||
Dibit[] dibits = getBuffer();
|
||||
|
||||
int[] bits = new int[dibits.length * 2];
|
||||
|
||||
for(int x = 0; x < dibits.length; x++)
|
||||
{
|
||||
if(dibits[x].getBit1())
|
||||
{
|
||||
bits[x * 2] = 1;
|
||||
}
|
||||
if(dibits[x].getBit2())
|
||||
{
|
||||
bits[x * 2 + 1] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the dibit into the internal circular buffer, overwriting the oldest dibit.
|
||||
*/
|
||||
public void put(Dibit dibit)
|
||||
{
|
||||
mBuffer[mPointer++] = dibit;
|
||||
|
||||
if(mPointer >= mBuffer.length)
|
||||
{
|
||||
mPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Places the dibit into the internal circular buffer, overwriting and returning the
|
||||
* oldest dibit.
|
||||
*/
|
||||
public Dibit getAndPut(Dibit dibit)
|
||||
{
|
||||
Dibit toReturn = mBuffer[mPointer];
|
||||
put(dibit);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the contents of the buffer
|
||||
*/
|
||||
public void log()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int counter = 0;
|
||||
int pointer = mPointer;
|
||||
|
||||
while(counter < mBuffer.length)
|
||||
{
|
||||
sb.append(mBuffer[pointer].getBit1() ? "1" : "0");
|
||||
sb.append(mBuffer[pointer++].getBit2() ? "1" : "0");
|
||||
|
||||
if(pointer >= mBuffer.length)
|
||||
{
|
||||
pointer = 0;
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
mLog.debug("BUFFER: " + sb + " Length:" + mBuffer.length);
|
||||
}
|
||||
}
|
||||
120
src/main/java/io/github/dsheirer/dsp/symbol/QPSKCarrierLock.java
Normal file
120
src/main/java/io/github/dsheirer/dsp/symbol/QPSKCarrierLock.java
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.symbol;
|
||||
|
||||
/**
|
||||
* QPSK carrier lock elements that indicate if a PLL is locked normally, or if it has locked at +/- 90 degrees,
|
||||
* or inverted 180 degrees. Abnormal PLL lock is normally detected by rotated sync patterns.
|
||||
*
|
||||
* Each enumeration entry provides a correct(dibit) method that supports correcting dibits that were captured with a
|
||||
* misaligned PLL lock to what they should be if the PLL were locked correctly. This allows detected (yet misaligned)
|
||||
* messages to become usable through dibit correction even though the PLL was misaligned.
|
||||
*/
|
||||
public enum QPSKCarrierLock
|
||||
{
|
||||
/**
|
||||
* Normal PLL lock indication
|
||||
*/
|
||||
NORMAL
|
||||
{
|
||||
@Override
|
||||
public Dibit correct(Dibit dibit)
|
||||
{
|
||||
//No correction needed
|
||||
return dibit;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* PLL lock at plus 90 degrees to the carrier, requiring a PLL adjustment of -90 degrees or -PI/2.
|
||||
*/
|
||||
PLUS_90
|
||||
{
|
||||
@Override
|
||||
public Dibit correct(Dibit dibit)
|
||||
{
|
||||
switch(dibit)
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return Dibit.D10_MINUS_1;
|
||||
case D01_PLUS_3:
|
||||
return Dibit.D00_PLUS_1;
|
||||
case D10_MINUS_1:
|
||||
return Dibit.D11_MINUS_3;
|
||||
case D11_MINUS_3:
|
||||
default:
|
||||
return Dibit.D01_PLUS_3;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* PLL lock at minus 90 degrees to the carrier, requiring a PLL adjustment of +90 degrees, or +PI/2.
|
||||
*/
|
||||
MINUS_90
|
||||
{
|
||||
@Override
|
||||
public Dibit correct(Dibit dibit)
|
||||
{
|
||||
switch(dibit)
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return Dibit.D01_PLUS_3;
|
||||
case D01_PLUS_3:
|
||||
return Dibit.D11_MINUS_3;
|
||||
case D10_MINUS_1:
|
||||
return Dibit.D00_PLUS_1;
|
||||
case D11_MINUS_3:
|
||||
default:
|
||||
return Dibit.D10_MINUS_1;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* PLL lock at 180 degrees or inverted to the carrier, requiring a PLL adjustment of 180 degrees or PI.
|
||||
*/
|
||||
INVERTED
|
||||
{
|
||||
@Override
|
||||
public Dibit correct(Dibit dibit)
|
||||
{
|
||||
switch(dibit)
|
||||
{
|
||||
case D00_PLUS_1:
|
||||
return Dibit.D11_MINUS_3;
|
||||
case D01_PLUS_3:
|
||||
return Dibit.D10_MINUS_1;
|
||||
case D10_MINUS_1:
|
||||
return Dibit.D01_PLUS_3;
|
||||
case D11_MINUS_3:
|
||||
default:
|
||||
return Dibit.D00_PLUS_1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Method for each entry to provide dibit correction solutions
|
||||
* @param dibit that was generated by a misaligned QPSK Phase locked loop
|
||||
* @return dibit that is corrected to the dibit for a correctly aligned QPSK Phase Locked Loop
|
||||
*/
|
||||
public abstract Dibit correct(Dibit dibit);
|
||||
}
|
||||
@@ -1,106 +1,106 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This 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/>
|
||||
******************************************************************************/
|
||||
|
||||
public class BCH_63_16_11 extends BerlekempMassey_63
|
||||
public class BCH_63_16_11 extends ReedSolomon_63_P25
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( BCH_63_16_11.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(BCH_63_16_11.class);
|
||||
|
||||
/**
|
||||
* BCH( 63,16,11) decoder
|
||||
*/
|
||||
public BCH_63_16_11()
|
||||
/**
|
||||
* BCH( 63,16,11) decoder
|
||||
*/
|
||||
public BCH_63_16_11()
|
||||
{
|
||||
/* TT = 11 = maximum correctable bit errors */
|
||||
super( 11 );
|
||||
super(63, 41);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs error detection and correction on the first 63 bits of the
|
||||
* message argument. If the message is correctable, only the first 16 bits
|
||||
* (information bits) are corrected.
|
||||
*
|
||||
* @return - true = success, false = failure
|
||||
*/
|
||||
public BinaryMessage correctNID( BinaryMessage message )
|
||||
{
|
||||
CRC status = CRC.PASSED;
|
||||
|
||||
int[] original = message.toReverseIntegerArray( 0, 62 );
|
||||
int[] corrected = new int[ 63 ];
|
||||
|
||||
boolean irrecoverableErrors = decode( original, corrected );
|
||||
/**
|
||||
* Performs error detection and correction on the first 63 bits of the
|
||||
* message argument. If the message is correctable, only the first 16 bits
|
||||
* (information bits) are corrected.
|
||||
*
|
||||
* @return - true = success, false = failure
|
||||
*/
|
||||
public BinaryMessage correctNID(BinaryMessage message)
|
||||
{
|
||||
CRC status = CRC.PASSED;
|
||||
|
||||
if( irrecoverableErrors )
|
||||
{
|
||||
message.setCRC( CRC.FAILED_CRC );
|
||||
|
||||
return message;
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int x = 0; x < 16; x++ )
|
||||
{
|
||||
int index = 63 - x - 1;
|
||||
|
||||
if( corrected[ index ] != original[ index ] )
|
||||
{
|
||||
status = CRC.CORRECTED;
|
||||
|
||||
if( corrected[ index ] == 1 )
|
||||
{
|
||||
message.set( x );
|
||||
}
|
||||
else
|
||||
{
|
||||
message.clear( x );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.setCRC( status );
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
String orig = "0010011000000011010010100000000110000111110011101010001010110000";
|
||||
String error = "0001010100000011010010100000000110000111100011001010001010110000";
|
||||
int[] original = message.toReverseIntegerArray(0, 62);
|
||||
int[] corrected = new int[63];
|
||||
|
||||
BinaryMessage errorMessage = BinaryMessage.load( error );
|
||||
boolean irrecoverableErrors = decode(original, corrected);
|
||||
|
||||
BCH_63_16_11 bch = new BCH_63_16_11();
|
||||
if(irrecoverableErrors)
|
||||
{
|
||||
message.setCRC(CRC.FAILED_CRC);
|
||||
|
||||
mLog.debug( "ORIG:" + orig );
|
||||
mLog.debug( " ERR:" + errorMessage.toString() );
|
||||
mLog.debug( " " );
|
||||
return message;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int x = 0; x < 16; x++)
|
||||
{
|
||||
int index = 63 - x - 1;
|
||||
|
||||
errorMessage = bch.correctNID( errorMessage );
|
||||
|
||||
mLog.debug( " CRC: " + errorMessage.getCRC().name() );
|
||||
mLog.debug( "CORR:" + errorMessage.toString() );
|
||||
mLog.debug( "ORIG:" + orig );
|
||||
}
|
||||
if(corrected[index] != original[index])
|
||||
{
|
||||
status = CRC.CORRECTED;
|
||||
|
||||
if(corrected[index] == 1)
|
||||
{
|
||||
message.set(x);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.clear(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.setCRC(status);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String orig = "0010011000000011010010100000000110000111110011101010001010110000";
|
||||
String error = "0001010100000011010010100000000110000111100011001010001010110000";
|
||||
|
||||
BinaryMessage errorMessage = BinaryMessage.load(error);
|
||||
|
||||
BCH_63_16_11 bch = new BCH_63_16_11();
|
||||
|
||||
mLog.debug("ORIG:" + orig);
|
||||
mLog.debug(" ERR:" + errorMessage.toString());
|
||||
mLog.debug(" ");
|
||||
|
||||
errorMessage = bch.correctNID(errorMessage);
|
||||
|
||||
mLog.debug(" CRC: " + errorMessage.getCRC().name());
|
||||
mLog.debug("CORR:" + errorMessage.toString());
|
||||
mLog.debug("ORIG:" + orig);
|
||||
}
|
||||
}
|
||||
|
||||
62
src/main/java/io/github/dsheirer/edac/BPTC_17_12_3.java
Normal file
62
src/main/java/io/github/dsheirer/edac/BPTC_17_12_3.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/*
|
||||
Adopted from https://github.com/boatbod/op25/blob/e5e8fe457240fb358a8163bcf6fe7dda5e4d8392/op25/gr-op25_repeater/lib/hamming.cc
|
||||
*/
|
||||
public class BPTC_17_12_3 {
|
||||
|
||||
public static boolean decode17123(BinaryMessage d, int offset)
|
||||
{
|
||||
if(d==null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate the checksum this column should have
|
||||
boolean c0 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(6 + offset) ^ d.get(7 + offset) ^ d.get(9 + offset);
|
||||
boolean c1 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(4 + offset) ^ d.get(7 + offset) ^ d.get(8 + offset) ^ d.get(10 + offset);
|
||||
boolean c2 = d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(3 + offset) ^ d.get(4 + offset) ^ d.get(5 + offset) ^ d.get(8 + offset) ^ d.get(9 + offset) ^ d.get(11 + offset);
|
||||
boolean c3 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(4 + offset) ^ d.get(5 + offset) ^ d.get(7 + offset) ^ d.get(10 + offset);
|
||||
boolean c4 = d.get(0 + offset) ^ d.get(1 + offset) ^ d.get(2 + offset) ^ d.get(5 + offset) ^ d.get(6 + offset) ^ d.get(8 + offset) ^ d.get(11 + offset);
|
||||
|
||||
// Compare these with the actual bits
|
||||
byte n = 0x00;
|
||||
n |= (c0 != d.get(12 + offset)) ? 0x01 : 0x00;
|
||||
n |= (c1 != d.get(13 + offset)) ? 0x02 : 0x00;
|
||||
n |= (c2 != d.get(14 + offset)) ? 0x04 : 0x00;
|
||||
n |= (c3 != d.get(15 + offset)) ? 0x08 : 0x00;
|
||||
n |= (c4 != d.get(16 + offset)) ? 0x10 : 0x00;
|
||||
|
||||
switch (n) {
|
||||
// Parity bit errors
|
||||
case 0x01: d.set(12 + offset, !d.get(12 + offset)); return true;
|
||||
case 0x02: d.set(13 + offset, !d.get(13 + offset)); return true;
|
||||
case 0x04: d.set(14 + offset, !d.get(14 + offset)); return true;
|
||||
case 0x08: d.set(15 + offset, !d.get(15 + offset)); return true;
|
||||
case 0x10: d.set(16 + offset, !d.get(16 + offset)); return true;
|
||||
|
||||
// Data bit errors
|
||||
case 0x1B: d.set(0 + offset, !d.get(0 + offset)); return true;
|
||||
case 0x1F: d.set(1 + offset, !d.get(1 + offset)); return true;
|
||||
case 0x17: d.set(2 + offset, !d.get(2 + offset)); return true;
|
||||
case 0x07: d.set(3 + offset, !d.get(3 + offset)); return true;
|
||||
case 0x0E: d.set(4 + offset, !d.get(4 + offset)); return true;
|
||||
case 0x1C: d.set(5 + offset, !d.get(5 + offset)); return true;
|
||||
case 0x11: d.set(6 + offset, !d.get(6 + offset)); return true;
|
||||
case 0x0B: d.set(7 + offset, !d.get(7 + offset)); return true;
|
||||
case 0x16: d.set(8 + offset, !d.get(8 + offset)); return true;
|
||||
case 0x05: d.set(9 + offset, !d.get(9 + offset)); return true;
|
||||
case 0x0A: d.set(10 + offset,!d.get(10 + offset)); return true;
|
||||
case 0x14: d.set(11 + offset,!d.get(11 + offset)); return true;
|
||||
|
||||
// No bit errors
|
||||
case 0x00: return true;
|
||||
|
||||
// Unrecoverable errors
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
284
src/main/java/io/github/dsheirer/edac/BPTC_196_96.java
Normal file
284
src/main/java/io/github/dsheirer/edac/BPTC_196_96.java
Normal file
@@ -0,0 +1,284 @@
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.BitSetFullException;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Block Product Turbo Code (BPTC) 196/96 Support
|
||||
*/
|
||||
public class BPTC_196_96
|
||||
{
|
||||
public static final int BPTC_LENGTH = 196;
|
||||
public static final int EXTRACTED_LENGTH = 96;
|
||||
public static final int MAX_ORIGINAL_INDEX = 136;
|
||||
public static final int COLUMN_COUNT = 15;
|
||||
public static final int MESSAGE_COLUMN_COUNT = 12; //Should be 11, but adjusted for the first pad bit
|
||||
public static final int CHECKSUM_COLUMN_COUNT = 4;
|
||||
public static final int MESSAGE_START_INDEX = 4;
|
||||
|
||||
/**
|
||||
* DMR De-interleaving indices
|
||||
*/
|
||||
public static int[] BPTC_DEINTERLEAVE = new int[]{0, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 16, 1, 182,
|
||||
167, 152, 137, 122, 107, 92, 77, 62, 47, 32, 17, 2, 183, 168, 153, 138, 123, 108, 93, 78, 63, 48, 33, 18, 3,
|
||||
184, 169, 154, 139, 124, 109, 94, 79, 64, 49, 34, 19, 4, 185, 170, 155, 140, 125, 110, 95, 80, 65, 50, 35, 20,
|
||||
5, 186, 171, 156, 141, 126, 111, 96, 81, 66, 51, 36, 21, 6, 187, 172, 157, 142, 127, 112, 97, 82, 67, 52, 37,
|
||||
22, 7, 188, 173, 158, 143, 128, 113, 98, 83, 68, 53, 38, 23, 8, 189, 174, 159, 144, 129, 114, 99, 84, 69, 54,
|
||||
39, 24, 9, 190, 175, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, 191, 176, 161, 146, 131, 116, 101, 86, 71,
|
||||
56, 41, 26, 11, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, 42, 27, 12, 193, 178, 163, 148, 133, 118, 103,
|
||||
88, 73, 58, 43, 28, 13, 194, 179, 164, 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 195, 180, 165, 150, 135, 120,
|
||||
105, 90, 75, 60, 45, 30, 15};
|
||||
|
||||
/**
|
||||
* DMR Column Indices
|
||||
*/
|
||||
public static int[][] COLUMN_INDEXES = new int[][]{
|
||||
{1, 16, 31, 46, 61, 76, 91, 106, 121, 136, 151, 166, 181},
|
||||
{2, 17, 32, 47, 62, 77, 92, 107, 122, 137, 152, 167, 182},
|
||||
{3, 18, 33, 48, 63, 78, 93, 108, 123, 138, 153, 168, 183},
|
||||
{4, 19, 34, 49, 64, 79, 94, 109, 124, 139, 154, 169, 184},
|
||||
{5, 20, 35, 50, 65, 80, 95, 110, 125, 140, 155, 170, 185},
|
||||
{6, 21, 36, 51, 66, 81, 96, 111, 126, 141, 156, 171, 186},
|
||||
{7, 22, 37, 52, 67, 82, 97, 112, 127, 142, 157, 172, 187},
|
||||
{8, 23, 38, 53, 68, 83, 98, 113, 128, 143, 158, 173, 188},
|
||||
{9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 159, 174, 189},
|
||||
{10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 175, 190},
|
||||
{11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 191},
|
||||
{12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192},
|
||||
{13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193},
|
||||
{14, 29, 44, 59, 74, 89, 104, 119, 134, 149, 164, 179, 194},
|
||||
{15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195}};
|
||||
|
||||
/**
|
||||
* Performs de-interleave, error detect and correct and extracts a 96-bit message from the DMR BPTC(196,96)
|
||||
* protected message
|
||||
* @param original uncorrected BPTC(196,96) message
|
||||
* @return error corrected and extracted 96-bit message
|
||||
*/
|
||||
public static CorrectedBinaryMessage extract(CorrectedBinaryMessage original)
|
||||
{
|
||||
//De-Interleave
|
||||
CorrectedBinaryMessage message = deinterleave(original);
|
||||
|
||||
//Perform error detection and correction
|
||||
correct(message);
|
||||
|
||||
//Extract the 96-bit message from the corrected 196 bits
|
||||
CorrectedBinaryMessage extracted = new CorrectedBinaryMessage(EXTRACTED_LENGTH);
|
||||
|
||||
//Transfer the corrected bit count
|
||||
extracted.setCorrectedBitCount(message.getCorrectedBitCount());
|
||||
|
||||
int index = MESSAGE_START_INDEX;
|
||||
|
||||
while(index < MAX_ORIGINAL_INDEX)
|
||||
{
|
||||
if((index % COLUMN_COUNT) < MESSAGE_COLUMN_COUNT)
|
||||
{
|
||||
try
|
||||
{
|
||||
extracted.add(message.get(index));
|
||||
}
|
||||
catch(BitSetFullException bsfe)
|
||||
{
|
||||
//Should never get to here
|
||||
bsfe.printStackTrace();
|
||||
return extracted;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
index += CHECKSUM_COLUMN_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
return extracted;
|
||||
}
|
||||
|
||||
/**
|
||||
* De-interleaves the transmitted bits of a DMR BPTC(196,96) protected message
|
||||
* @param message that contains interleaved (transmitted) message
|
||||
* @return de-interleaved message
|
||||
*/
|
||||
private static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage message)
|
||||
{
|
||||
CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BPTC_LENGTH);
|
||||
for(int x = 0; x < BPTC_DEINTERLEAVE.length; x++)
|
||||
{
|
||||
deinterleaved.set(x, message.get(BPTC_DEINTERLEAVE[x]));
|
||||
}
|
||||
|
||||
return deinterleaved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the error indices from the row level Hamming(15,11,3) protected words
|
||||
* @param message containing protected words in rows
|
||||
* @return list of zero or more error indices
|
||||
*/
|
||||
private static List<Integer> getRowErrors(CorrectedBinaryMessage message)
|
||||
{
|
||||
List<Integer> rowErrors = new ArrayList<>();
|
||||
|
||||
int offset = 0;
|
||||
int index = -1;
|
||||
|
||||
for(int row = 0; row < 13; row++)
|
||||
{
|
||||
//The first bit of the message is a pad/reserved bit and is ignored.
|
||||
offset = (row * 15) + 1;
|
||||
index = Hamming15.getErrorIndex(message, offset);
|
||||
|
||||
if(index >= 0)
|
||||
{
|
||||
rowErrors.add(index);
|
||||
}
|
||||
}
|
||||
|
||||
//Note: row errors should already be in order and don't need sorted
|
||||
return rowErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the error indices from the column level Hamming(13,9,3) protected words
|
||||
* @param message containing protected words in columns
|
||||
* @return list of zero or more error indices
|
||||
*/
|
||||
private static List<Integer> getColumnErrors(CorrectedBinaryMessage message)
|
||||
{
|
||||
List<Integer> columnErrors = new ArrayList<>();
|
||||
|
||||
int index = -1;
|
||||
|
||||
for(int column = 0; column < 15; column++)
|
||||
{
|
||||
index = Hamming13.getErrorIndex(message, COLUMN_INDEXES[column]);
|
||||
|
||||
if(index >= 0)
|
||||
{
|
||||
columnErrors.add(index);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(columnErrors);
|
||||
return columnErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs error detection and correction on the DMR BPTC(196,96) protected message
|
||||
*
|
||||
* @param message to process and extract
|
||||
*/
|
||||
private static void correct(CorrectedBinaryMessage message)
|
||||
{
|
||||
List<Integer> rowErrors = getRowErrors(message);
|
||||
|
||||
if(!rowErrors.isEmpty())
|
||||
{
|
||||
List<Integer> columnErrors = getColumnErrors(message);
|
||||
|
||||
if(matches(rowErrors, columnErrors))
|
||||
{
|
||||
for(Integer index: rowErrors)
|
||||
{
|
||||
message.flip(index);
|
||||
}
|
||||
|
||||
message.setCorrectedBitCount(rowErrors.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
//Step 1: correct row errors and recalculate column errors (should be zero)
|
||||
for(Integer index: rowErrors)
|
||||
{
|
||||
message.flip(index);
|
||||
}
|
||||
|
||||
List<Integer> columnErrorsPart2 = getColumnErrors(message);
|
||||
|
||||
if(columnErrorsPart2.isEmpty())
|
||||
{
|
||||
//Success
|
||||
message.setCorrectedBitCount(rowErrors.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
//Reverse the row error correction
|
||||
for(Integer index: rowErrors)
|
||||
{
|
||||
message.flip(index);
|
||||
}
|
||||
|
||||
//Step 2: correct column errors and recalculate row errors (should be zero)
|
||||
for(Integer index: columnErrors)
|
||||
{
|
||||
message.flip(index);
|
||||
}
|
||||
|
||||
List<Integer> rowErrorsPart2 = getRowErrors(message);
|
||||
|
||||
if(rowErrorsPart2.isEmpty())
|
||||
{
|
||||
//Success
|
||||
message.setCorrectedBitCount(columnErrors.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
//Reverse the column error correction
|
||||
for(Integer index: columnErrors)
|
||||
{
|
||||
message.flip(index);
|
||||
}
|
||||
|
||||
//Set the error count greater than the maximum correctable column errors (16)
|
||||
message.setCorrectedBitCount(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the errors detected by the row protected words match the errors detected by the column protected words
|
||||
* @param rowErrors bit positions
|
||||
* @param columnErrors bit positions
|
||||
* @return true if they have the same quantity of bit position errors and the bit positions match
|
||||
*/
|
||||
private static boolean matches(List<Integer> rowErrors, List<Integer> columnErrors)
|
||||
{
|
||||
if(rowErrors.size() == columnErrors.size())
|
||||
{
|
||||
for(int x = 0; x < rowErrors.size(); x++)
|
||||
{
|
||||
if(!rowErrors.get(x).equals(columnErrors.get(0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String raw = "0000000001100010100101100011100100110100110101000100000011101001110001001000100001001010010100001000011111011000000011110000111000111111100011000011100001100000000101001010000100010111010100111001";
|
||||
System.out.println("RAW:" + raw);
|
||||
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(BinaryMessage.load(raw));
|
||||
System.out.println("LOD:" + message.toString());
|
||||
CorrectedBinaryMessage extracted = extract(message);
|
||||
System.out.println("EXT:" + extracted.toString());
|
||||
}
|
||||
}
|
||||
559
src/main/java/io/github/dsheirer/edac/BerlekempMassey.java
Normal file
559
src/main/java/io/github/dsheirer/edac/BerlekempMassey.java
Normal file
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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.edac;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Berlekemp Massey decoder for primitive RS/BCH block codes
|
||||
*
|
||||
* Original C implementation by Simon Rockliff, 26 June 1991
|
||||
*/
|
||||
public class BerlekempMassey
|
||||
{
|
||||
public final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
|
||||
|
||||
/* Golay field size GF( 2 ** MM ) */
|
||||
private int MM;
|
||||
|
||||
/* Codeword Length: NN = 2 ** MM - 1 */
|
||||
private int NN;
|
||||
private int MAX_NN;
|
||||
|
||||
/* Hamming distance between codewords: NN - KK + 1 = 2 * TT + 1 */
|
||||
private int KK;
|
||||
|
||||
/* Maximum number of errors that can be corrected */
|
||||
int TT;
|
||||
|
||||
int[] alpha_to;
|
||||
int[] index_of;
|
||||
int[] gg;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param galoisFieldSize as a power of 2, e.g. for a GF(2^6) the parameter would be 6
|
||||
* @param maxCorrectableErrors is Hamming Distance / 2. e.g. RS(24,16,9) would be (24 - 16) / 2, or floor(9/2).
|
||||
* @param generatorPolynomial for the Galois Field represented in binary as 0/1 integers where the array is in
|
||||
* big-endian format. For example: polynomial a6 + a1 + 1 is 1000011, as big endian: 1100001, and in array
|
||||
* form as {1,1,0,0,0,0,1}
|
||||
*/
|
||||
public BerlekempMassey(int galoisFieldSize, int maxCorrectableErrors, int[] generatorPolynomial)
|
||||
{
|
||||
//TODO: fix this
|
||||
MM = galoisFieldSize;
|
||||
NN = (int)FastMath.pow(2, MM) - 1;
|
||||
TT = maxCorrectableErrors;
|
||||
KK = NN - 2 * TT;
|
||||
|
||||
}
|
||||
|
||||
public BerlekempMassey(int bitSize, int n, int k, int[] generatorPolynomial)
|
||||
{
|
||||
Validate.isTrue(bitSize == generatorPolynomial.length - 1,
|
||||
"Generator polynomial length must be: bitSize + 1");
|
||||
|
||||
MM = bitSize;
|
||||
NN = n;
|
||||
KK = k;
|
||||
TT = (int)Math.floor((NN - KK) / 2);
|
||||
|
||||
//Since this may be a shortened code, setup arrays to the max size of NN
|
||||
int maxNN = (int)Math.pow(2, MM) - 1;
|
||||
|
||||
alpha_to = new int[maxNN + 1];
|
||||
index_of = new int[maxNN + 1];
|
||||
|
||||
gg = new int[NN - KK + 1];
|
||||
|
||||
generate_gf(generatorPolynomial);
|
||||
|
||||
gen_poly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the Golay Field.
|
||||
*
|
||||
* Generates a GF( 2**mm ) from the irreducible polynomial
|
||||
* p(X) in pp[0]..pp[mm]
|
||||
*
|
||||
* Lookup tables:
|
||||
* index_of[] = polynomial form
|
||||
* alpha_to[] = contains j=alpha**i;
|
||||
*
|
||||
* Polynomial form -> Index form index_of[j=alpha**i] = i
|
||||
*
|
||||
* alpha_to = 2 is the primitive element of GF( 2**mm )
|
||||
*
|
||||
* @param generator_polynomial
|
||||
*/
|
||||
private void generate_gf(int[] generator_polynomial)
|
||||
{
|
||||
int i;
|
||||
int mask = 1;
|
||||
|
||||
alpha_to[MM] = 0;
|
||||
|
||||
for(i = 0; i < MM; i++)
|
||||
{
|
||||
alpha_to[i] = mask;
|
||||
index_of[alpha_to[i]] = i;
|
||||
|
||||
if(generator_polynomial[i] != 0)
|
||||
{
|
||||
alpha_to[MM] ^= mask;
|
||||
}
|
||||
|
||||
mask <<= 1;
|
||||
}
|
||||
|
||||
index_of[alpha_to[MM]] = MM;
|
||||
|
||||
mask >>= 1;
|
||||
|
||||
for(i = MM + 1; i < NN; i++)
|
||||
{
|
||||
if(alpha_to[i - 1] >= mask)
|
||||
{
|
||||
alpha_to[i] = alpha_to[MM] ^ ((alpha_to[i - 1] ^ mask) << 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
alpha_to[i] = alpha_to[i - 1] << 1;
|
||||
}
|
||||
|
||||
index_of[alpha_to[i]] = i;
|
||||
}
|
||||
|
||||
index_of[0] = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the polynomial for a TT-error correction code.
|
||||
*
|
||||
* Length NN = ( 2 ** MM -1 ) Reed Solomon code from the product of
|
||||
* (X+alpha**i), i=1..2*tt
|
||||
*/
|
||||
private void gen_poly()
|
||||
{
|
||||
int i, j;
|
||||
|
||||
gg[0] = 2; /* primitive element alpha = 2 for GF(2**mm) */
|
||||
gg[1] = 1; /* g(x) = (X+alpha) initially */
|
||||
for(i = 2; i <= (NN - KK); i++)
|
||||
{
|
||||
gg[i] = 1;
|
||||
|
||||
for(j = i - 1; j > 0; j--)
|
||||
{
|
||||
if(gg[j] != 0)
|
||||
{
|
||||
gg[j] = gg[j - 1] ^ alpha_to[(index_of[gg[j]] + i) % NN];
|
||||
}
|
||||
else
|
||||
{
|
||||
gg[j] = gg[j - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/* gg[0] can never be zero */
|
||||
gg[0] = alpha_to[(index_of[gg[0]] + i) % NN];
|
||||
}
|
||||
|
||||
/* convert gg[] to index form for quicker encoding */
|
||||
for(i = 0; i <= (NN - KK); i++)
|
||||
{
|
||||
gg[i] = index_of[gg[i]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes
|
||||
*
|
||||
* @param input
|
||||
* @param output
|
||||
* @return
|
||||
*/
|
||||
/* assume we have received bits grouped into mm-bit symbols in recd[i],
|
||||
i=0..(nn-1), and recd[i] is polynomial form.
|
||||
We first compute the 2*tt syndromes by substituting alpha**i into rec(X) and
|
||||
evaluating, storing the syndromes in s[i], i=1..2tt (leave s[0] zero) .
|
||||
Then we use the Berlekamp iteration to find the error location polynomial
|
||||
elp[i]. If the degree of the elp is >tt, we cannot correct all the errors
|
||||
and hence just put out the information symbols uncorrected. If the degree of
|
||||
elp is <=tt, we substitute alpha**i , i=1..n into the elp to get the roots,
|
||||
hence the inverse roots, the error location numbers. If the number of errors
|
||||
located does not equal the degree of the elp, we have more than tt errors
|
||||
and cannot correct them. Otherwise, we then solve for the error value at
|
||||
the error location and correct the error. The procedure is that found in
|
||||
Lin and Costello. For the cases where the number of errors is known to be too
|
||||
large to correct, the information symbols as received are output (the
|
||||
advantage of systematic encoding is that hopefully some of the information
|
||||
symbols will be okay and that if we are in luck, the errors are in the
|
||||
parity part of the transmitted codeword). Of course, these insoluble cases
|
||||
can be returned as error flags to the calling routine if desired. */
|
||||
public boolean decode(final int[] input, int[] output) //input, output
|
||||
{
|
||||
int u, q;
|
||||
int[][] elp = new int[NN - KK + 2][NN - KK];
|
||||
int[] d = new int[NN - KK + 2];
|
||||
int[] l = new int[NN - KK + 2];
|
||||
int[] u_lu = new int[NN - KK + 2];
|
||||
int[] s = new int[NN - KK + 1];
|
||||
int count = 0;
|
||||
boolean syn_error = false;
|
||||
int[] root = new int[TT];
|
||||
int[] loc = new int[TT];
|
||||
int[] z = new int[TT + 1];
|
||||
int[] err = new int[NN];
|
||||
int[] reg = new int[TT + 1];
|
||||
|
||||
boolean irrecoverable_error = false;
|
||||
|
||||
/* put recd[i] into index form (ie as powers of alpha) */
|
||||
for(int i = 0; i < NN; i++)
|
||||
{
|
||||
output[i] = index_of[input[i]];
|
||||
}
|
||||
|
||||
/* first form the syndromes */
|
||||
for(int i = 1; i <= NN - KK; i++)
|
||||
{
|
||||
s[i] = 0;
|
||||
|
||||
for(int j = 0; j < NN; j++)
|
||||
{
|
||||
if(output[j] != -1)
|
||||
{
|
||||
/* recd[j] in index form */
|
||||
s[i] ^= alpha_to[(output[j] + i * j) % NN];
|
||||
}
|
||||
}
|
||||
|
||||
/* convert syndrome from polynomial form to index form */
|
||||
if(s[i] != 0)
|
||||
{
|
||||
/* set flag if non-zero syndrome => error */
|
||||
syn_error = true;
|
||||
}
|
||||
|
||||
s[i] = index_of[s[i]];
|
||||
}
|
||||
|
||||
if(syn_error) /* if errors, try and correct */
|
||||
{
|
||||
/* compute the error location polynomial via the Berlekamp iterative algorithm,
|
||||
following the terminology of Lin and Costello : d[u] is the 'mu'th
|
||||
discrepancy, where u='mu'+1 and 'mu' (the Greek letter!) is the step number
|
||||
ranging from -1 to 2*tt (see L&C), l[u] is the
|
||||
degree of the elp at that step, and u_l[u] is the difference between the
|
||||
step number and the degree of the elp.
|
||||
*/
|
||||
|
||||
/* initialise table entries */
|
||||
d[0] = 0; /* index form */
|
||||
d[1] = s[1]; /* index form */
|
||||
elp[0][0] = 0; /* index form */
|
||||
elp[1][0] = 1; /* polynomial form */
|
||||
|
||||
for(int i = 1; i < NN - KK; i++)
|
||||
{
|
||||
elp[0][i] = -1; /* index form */
|
||||
elp[1][i] = 0; /* polynomial form */
|
||||
}
|
||||
|
||||
l[0] = 0;
|
||||
l[1] = 0;
|
||||
u_lu[0] = -1;
|
||||
u_lu[1] = 0;
|
||||
u = 0;
|
||||
|
||||
do
|
||||
{
|
||||
u++;
|
||||
|
||||
if(d[u] == -1)
|
||||
{
|
||||
l[u + 1] = l[u];
|
||||
|
||||
for(int i = 0; i <= l[u]; i++)
|
||||
{
|
||||
elp[u + 1][i] = elp[u][i];
|
||||
elp[u][i] = index_of[elp[u][i]];
|
||||
}
|
||||
}
|
||||
else
|
||||
/* search for words with greatest u_lu[q] for which d[q]!=0 */
|
||||
{
|
||||
q = u - 1;
|
||||
|
||||
while((d[q] == -1) && (q > 0))
|
||||
{
|
||||
q--;
|
||||
}
|
||||
|
||||
/* have found first non-zero d[q] */
|
||||
if(q > 0)
|
||||
{
|
||||
int j = q;
|
||||
|
||||
do
|
||||
{
|
||||
j--;
|
||||
|
||||
if((d[j] != -1) && (u_lu[q] < u_lu[j]))
|
||||
{
|
||||
q = j;
|
||||
}
|
||||
}
|
||||
while(j > 0);
|
||||
}
|
||||
;
|
||||
|
||||
/* have now found q such that d[u]!=0 and u_lu[q] is maximum */
|
||||
/* store degree of new elp polynomial */
|
||||
l[u + 1] = FastMath.max(l[u], l[q] + u - q);
|
||||
|
||||
/* form new elp(x) */
|
||||
for(int i = 0; i < NN - KK; i++)
|
||||
{
|
||||
elp[u + 1][i] = 0;
|
||||
}
|
||||
|
||||
for(int i = 0; i <= l[q]; i++)
|
||||
{
|
||||
if(elp[q][i] != -1)
|
||||
{
|
||||
elp[u + 1][i + u - q] =
|
||||
alpha_to[(d[u] + NN - d[q]
|
||||
+ elp[q][i]) % NN];
|
||||
}
|
||||
}
|
||||
for(int i = 0; i <= l[u]; i++)
|
||||
{
|
||||
elp[u + 1][i] ^= elp[u][i];
|
||||
elp[u][i] = index_of[elp[u][i]]; /*convert old elp value to index*/
|
||||
}
|
||||
}
|
||||
|
||||
u_lu[u + 1] = u - l[u + 1];
|
||||
|
||||
/* form (u+1)th discrepancy */
|
||||
if(u < NN - KK) /* no discrepancy computed on last iteration */
|
||||
{
|
||||
if(s[u + 1] != -1)
|
||||
{
|
||||
d[u + 1] = alpha_to[s[u + 1]];
|
||||
}
|
||||
else
|
||||
{
|
||||
d[u + 1] = 0;
|
||||
}
|
||||
for(int i = 1; i <= l[u + 1]; i++)
|
||||
{
|
||||
if((s[u + 1 - i] != -1) && (elp[u + 1][i] != 0))
|
||||
{
|
||||
d[u + 1] ^= alpha_to[(s[u + 1 - i]
|
||||
+ index_of[elp[u + 1][i]]) % NN];
|
||||
}
|
||||
}
|
||||
|
||||
d[u + 1] = index_of[d[u + 1]]; /* put d[u+1] into index form */
|
||||
}
|
||||
}
|
||||
while((u < NN - KK) && (l[u + 1] <= TT));
|
||||
|
||||
u++;
|
||||
|
||||
if(l[u] <= TT) /* can correct error */
|
||||
{
|
||||
/* put elp into index form */
|
||||
for(int i = 0; i <= l[u]; i++)
|
||||
{
|
||||
elp[u][i] = index_of[elp[u][i]];
|
||||
}
|
||||
|
||||
/* find roots of the error location polynomial */
|
||||
if(l[u] >= 0)
|
||||
{
|
||||
System.arraycopy(elp[u], 1, reg, 1, l[u]);
|
||||
}
|
||||
|
||||
count = 0;
|
||||
|
||||
for(int i = 1; i <= NN; i++)
|
||||
{
|
||||
q = 1;
|
||||
|
||||
for(int j = 1; j <= l[u]; j++)
|
||||
{
|
||||
if(reg[j] != -1)
|
||||
{
|
||||
reg[j] = (reg[j] + j) % NN;
|
||||
q ^= alpha_to[reg[j]];
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
if(q == 0) /* store root and error location number indices */
|
||||
{
|
||||
root[count] = i;
|
||||
loc[count] = NN - i;
|
||||
count++;
|
||||
}
|
||||
;
|
||||
}
|
||||
;
|
||||
|
||||
if(count == l[u]) /* no. roots = degree of elp hence <= tt errors */
|
||||
{
|
||||
/* form polynomial z(x) */
|
||||
for(int i = 1; i <= l[u]; i++) /* Z[0] = 1 always - do not need */
|
||||
{
|
||||
if((s[i] != -1) && (elp[u][i] != -1))
|
||||
{
|
||||
z[i] = alpha_to[s[i]] ^ alpha_to[elp[u][i]];
|
||||
}
|
||||
else if((s[i] != -1) && (elp[u][i] == -1))
|
||||
{
|
||||
z[i] = alpha_to[s[i]];
|
||||
}
|
||||
else if((s[i] == -1) && (elp[u][i] != -1))
|
||||
{
|
||||
z[i] = alpha_to[elp[u][i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
z[i] = 0;
|
||||
}
|
||||
|
||||
for(int j = 1; j < i; j++)
|
||||
{
|
||||
if((s[j] != -1) && (elp[u][i - j] != -1))
|
||||
{
|
||||
z[i] ^= alpha_to[(elp[u][i - j] + s[j]) % NN];
|
||||
}
|
||||
}
|
||||
|
||||
z[i] = index_of[z[i]]; /* put into index form */
|
||||
}
|
||||
;
|
||||
|
||||
/* evaluate errors at locations given by error location numbers loc[i] */
|
||||
for(int i = 0; i < NN; i++)
|
||||
{
|
||||
err[i] = 0;
|
||||
|
||||
if(output[i] != -1) /* convert recd[] to polynomial form */
|
||||
{
|
||||
output[i] = alpha_to[output[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
output[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < l[u]; i++) /* compute numerator of error term first */
|
||||
{
|
||||
err[loc[i]] = 1; /* accounts for z[0] */
|
||||
|
||||
for(int j = 1; j <= l[u]; j++)
|
||||
{
|
||||
if(z[j] != -1)
|
||||
{
|
||||
err[loc[i]] ^= alpha_to[(z[j] + j * root[i]) % NN];
|
||||
}
|
||||
}
|
||||
|
||||
if(err[loc[i]] != 0)
|
||||
{
|
||||
err[loc[i]] = index_of[err[loc[i]]];
|
||||
|
||||
q = 0; /* form denominator of error term */
|
||||
|
||||
for(int j = 0; j < l[u]; j++)
|
||||
{
|
||||
if(j != i)
|
||||
{
|
||||
q += index_of[1 ^ alpha_to[(loc[j] + root[i]) % NN]];
|
||||
}
|
||||
}
|
||||
|
||||
q = q % NN;
|
||||
err[loc[i]] = alpha_to[(err[loc[i]] - q + NN) % NN];
|
||||
output[loc[i]] ^= err[loc[i]]; /*recd[i] must be in polynomial form */
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no. roots != degree of elp => >tt errors and cannot solve */
|
||||
irrecoverable_error = true;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
/* elp has degree >tt hence cannot solve */
|
||||
irrecoverable_error = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no non-zero syndromes => no errors: output received codeword */
|
||||
for(int i = 0; i < NN; i++)
|
||||
{
|
||||
if(output[i] != -1) /* convert recd[] to polynomial form */
|
||||
{
|
||||
output[i] = alpha_to[output[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
output[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irrecoverable_error)
|
||||
{
|
||||
for(int i = 0; i < NN; i++) /* could return error flag if desired */
|
||||
{
|
||||
if(output[i] != -1) /* convert recd[] to polynomial form */
|
||||
{
|
||||
output[i] = alpha_to[output[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
output[i] = 0; /* just output received codeword as is */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return irrecoverable_error;
|
||||
}
|
||||
}
|
||||
@@ -1,521 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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.edac;
|
||||
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Berlekemp Massey decoder for 63-bit primitive RS/BCH block codes
|
||||
*/
|
||||
public class BerlekempMassey_63
|
||||
{
|
||||
public final static Logger mLog = LoggerFactory.getLogger( BerlekempMassey_63.class );
|
||||
|
||||
/* Golay field size GF( 2 ** MM ) */
|
||||
private static final int MM = 6;
|
||||
|
||||
/* Codeword Length: NN = 2 ** MM - 1 */
|
||||
private static final int NN = 63;
|
||||
|
||||
/* Hamming distance between codewords: NN - KK + 1 = 2 * TT + 1 */
|
||||
private int KK;
|
||||
|
||||
/* Maximum number of errors that can be corrected */
|
||||
int TT;
|
||||
|
||||
int[] alpha_to;
|
||||
int[] index_of;
|
||||
int[] gg;
|
||||
|
||||
public BerlekempMassey_63( int tt )
|
||||
{
|
||||
TT = tt;
|
||||
KK = NN - 2 * TT;
|
||||
|
||||
alpha_to = new int[ NN + 1 ];
|
||||
index_of = new int[ NN + 1 ];
|
||||
gg = new int[ NN - KK + 1 ];
|
||||
|
||||
/* P25 generator polynomial */
|
||||
int generator_polinomial[] = { 1, 1, 0, 0, 0, 0, 1 };
|
||||
|
||||
generate_gf( generator_polinomial );
|
||||
|
||||
gen_poly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the Golay Field.
|
||||
*
|
||||
* Generates a GF( 2**mm ) from the irreducible polynomial
|
||||
* p(X) in pp[0]..pp[mm]
|
||||
*
|
||||
* Lookup tables:
|
||||
* index_of[] = polynomial form
|
||||
* alpha_to[] = contains j=alpha**i;
|
||||
*
|
||||
* Polynomial form -> Index form index_of[j=alpha**i] = i
|
||||
*
|
||||
* alpha_to = 2 is the primitive element of GF( 2**mm )
|
||||
*
|
||||
* @param generator_polynomial
|
||||
*/
|
||||
private void generate_gf( int[] generator_polynomial )
|
||||
{
|
||||
int i;
|
||||
int mask = 1;
|
||||
|
||||
alpha_to[ MM ] = 0;
|
||||
|
||||
for( i = 0; i < MM; i++ )
|
||||
{
|
||||
alpha_to[ i ] = mask;
|
||||
index_of[ alpha_to[ i ] ] = i;
|
||||
|
||||
if( generator_polynomial[ i ] != 0 )
|
||||
{
|
||||
alpha_to[ MM ] ^= mask;
|
||||
}
|
||||
|
||||
mask <<= 1;
|
||||
}
|
||||
|
||||
index_of[ alpha_to[ MM ] ] = MM;
|
||||
|
||||
mask >>= 1;
|
||||
|
||||
for ( i = MM + 1; i < NN; i++ )
|
||||
{
|
||||
if( alpha_to[ i - 1 ] >= mask )
|
||||
{
|
||||
alpha_to[ i ] = alpha_to[ MM ] ^ ( ( alpha_to[ i - 1 ] ^ mask ) << 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
alpha_to[ i ] = alpha_to[ i - 1 ] << 1;
|
||||
}
|
||||
|
||||
index_of[ alpha_to[ i ] ] = i;
|
||||
}
|
||||
|
||||
index_of[ 0 ] = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the polynomial for a TT-error correction code.
|
||||
*
|
||||
* Length NN = ( 2 ** MM -1 ) Reed Solomon code from the product of
|
||||
* (X+alpha**i), i=1..2*tt
|
||||
*/
|
||||
private void gen_poly()
|
||||
{
|
||||
int i, j;
|
||||
|
||||
gg[ 0 ] = 2; /* primitive element alpha = 2 for GF(2**mm) */
|
||||
gg[ 1 ] = 1; /* g(x) = (X+alpha) initially */
|
||||
for( i = 2; i <= NN - KK; i++ )
|
||||
{
|
||||
gg[ i ] = 1;
|
||||
|
||||
for( j = i - 1; j > 0; j-- )
|
||||
{
|
||||
if( gg[ j ] != 0 )
|
||||
{
|
||||
gg[ j ] = gg[ j - 1 ] ^ alpha_to[ ( index_of[ gg[ j ] ] + i ) % NN ];
|
||||
}
|
||||
else
|
||||
{
|
||||
gg[ j ] = gg[ j - 1 ];
|
||||
}
|
||||
}
|
||||
|
||||
/* gg[0] can never be zero */
|
||||
gg[ 0 ] = alpha_to[ ( index_of[ gg[ 0 ] ] + i ) % NN ];
|
||||
}
|
||||
|
||||
/* convert gg[] to index form for quicker encoding */
|
||||
for( i = 0; i <= NN - KK; i++ )
|
||||
{
|
||||
gg[ i ] = index_of[ gg[ i ] ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes
|
||||
* @param input
|
||||
* @param output
|
||||
* @return
|
||||
*/
|
||||
/* assume we have received bits grouped into mm-bit symbols in recd[i],
|
||||
i=0..(nn-1), and recd[i] is polynomial form.
|
||||
We first compute the 2*tt syndromes by substituting alpha**i into rec(X) and
|
||||
evaluating, storing the syndromes in s[i], i=1..2tt (leave s[0] zero) .
|
||||
Then we use the Berlekamp iteration to find the error location polynomial
|
||||
elp[i]. If the degree of the elp is >tt, we cannot correct all the errors
|
||||
and hence just put out the information symbols uncorrected. If the degree of
|
||||
elp is <=tt, we substitute alpha**i , i=1..n into the elp to get the roots,
|
||||
hence the inverse roots, the error location numbers. If the number of errors
|
||||
located does not equal the degree of the elp, we have more than tt errors
|
||||
and cannot correct them. Otherwise, we then solve for the error value at
|
||||
the error location and correct the error. The procedure is that found in
|
||||
Lin and Costello. For the cases where the number of errors is known to be too
|
||||
large to correct, the information symbols as received are output (the
|
||||
advantage of systematic encoding is that hopefully some of the information
|
||||
symbols will be okay and that if we are in luck, the errors are in the
|
||||
parity part of the transmitted codeword). Of course, these insoluble cases
|
||||
can be returned as error flags to the calling routine if desired. */
|
||||
public boolean decode( final int[] input, int[] output ) //input, output
|
||||
{
|
||||
int u, q;
|
||||
int[][] elp = new int[ NN - KK + 2 ][ NN - KK ];
|
||||
int[] d = new int[ NN - KK + 2 ];
|
||||
int[] l = new int[ NN - KK + 2 ];
|
||||
int[] u_lu = new int[ NN - KK + 2 ];
|
||||
int[] s = new int[ NN - KK + 1 ];
|
||||
int count = 0;
|
||||
boolean syn_error = false;
|
||||
int[] root = new int[ TT ];
|
||||
int[] loc = new int[ TT ];
|
||||
int[] z = new int[ TT + 1 ];
|
||||
int[] err = new int[ NN ];
|
||||
int[] reg = new int[ TT + 1 ];
|
||||
|
||||
boolean irrecoverable_error = false;
|
||||
|
||||
/* put recd[i] into index form (ie as powers of alpha) */
|
||||
for( int i = 0; i < NN; i++ )
|
||||
{
|
||||
output[ i ] = index_of[ input[ i ] ];
|
||||
}
|
||||
|
||||
/* first form the syndromes */
|
||||
for( int i = 1; i <= NN - KK; i++ )
|
||||
{
|
||||
s[ i ] = 0;
|
||||
|
||||
for( int j = 0; j < NN; j++ )
|
||||
{
|
||||
if( output[ j ] != -1 )
|
||||
{
|
||||
/* recd[j] in index form */
|
||||
s[ i ] ^= alpha_to[ ( output[ j ] + i * j ) % NN ];
|
||||
}
|
||||
}
|
||||
|
||||
/* convert syndrome from polynomial form to index form */
|
||||
if( s[ i ] != 0 )
|
||||
{
|
||||
/* set flag if non-zero syndrome => error */
|
||||
syn_error = true;
|
||||
}
|
||||
|
||||
s[ i ] = index_of[ s[ i ] ];
|
||||
}
|
||||
|
||||
if( syn_error ) /* if errors, try and correct */
|
||||
{
|
||||
/* compute the error location polynomial via the Berlekamp iterative algorithm,
|
||||
following the terminology of Lin and Costello : d[u] is the 'mu'th
|
||||
discrepancy, where u='mu'+1 and 'mu' (the Greek letter!) is the step number
|
||||
ranging from -1 to 2*tt (see L&C), l[u] is the
|
||||
degree of the elp at that step, and u_l[u] is the difference between the
|
||||
step number and the degree of the elp.
|
||||
*/
|
||||
|
||||
/* initialise table entries */
|
||||
d[ 0 ] = 0; /* index form */
|
||||
d[ 1 ] = s[ 1 ]; /* index form */
|
||||
elp[ 0 ][ 0 ] = 0; /* index form */
|
||||
elp[ 1 ][ 0 ] = 1; /* polynomial form */
|
||||
|
||||
for( int i = 1; i < NN - KK; i++ )
|
||||
{
|
||||
elp[ 0 ][ i ] = -1; /* index form */
|
||||
elp[ 1 ][ i ] = 0; /* polynomial form */
|
||||
}
|
||||
|
||||
l[ 0 ] = 0;
|
||||
l[ 1 ] = 0;
|
||||
u_lu[ 0 ] = -1;
|
||||
u_lu[ 1 ] = 0;
|
||||
u = 0;
|
||||
|
||||
do
|
||||
{
|
||||
u++;
|
||||
|
||||
if( d[ u ] == -1 )
|
||||
{
|
||||
l[ u + 1 ] = l[ u ];
|
||||
|
||||
for( int i = 0; i <= l[ u ]; i++ )
|
||||
{
|
||||
elp[ u + 1 ][ i ] = elp[ u ][ i ];
|
||||
elp[ u ][ i ] = index_of[ elp[ u ][ i ] ];
|
||||
}
|
||||
}
|
||||
else
|
||||
/* search for words with greatest u_lu[q] for which d[q]!=0 */
|
||||
{
|
||||
q = u - 1;
|
||||
|
||||
while( ( d[ q ] == -1 ) && ( q > 0 ) )
|
||||
{
|
||||
q--;
|
||||
}
|
||||
|
||||
/* have found first non-zero d[q] */
|
||||
if( q > 0 )
|
||||
{
|
||||
int j = q;
|
||||
|
||||
do
|
||||
{
|
||||
j--;
|
||||
|
||||
if( ( d[ j ] != -1 ) && ( u_lu[ q ] < u_lu[ j ] ) )
|
||||
{
|
||||
q = j;
|
||||
}
|
||||
}
|
||||
while( j > 0 );
|
||||
};
|
||||
|
||||
/* have now found q such that d[u]!=0 and u_lu[q] is maximum */
|
||||
/* store degree of new elp polynomial */
|
||||
l[u + 1] = FastMath.max(l[u], l[q] + u - q);
|
||||
|
||||
/* form new elp(x) */
|
||||
for( int i = 0; i < NN - KK; i++ )
|
||||
{
|
||||
elp[ u + 1 ][ i ] = 0;
|
||||
}
|
||||
|
||||
for( int i = 0; i <= l[q]; i++ )
|
||||
{
|
||||
if( elp[ q ][ i ] != -1 )
|
||||
{
|
||||
elp[ u + 1 ][ i + u - q ] =
|
||||
alpha_to[ ( d[ u ] + NN - d[ q ]
|
||||
+ elp[ q ][ i ]) % NN ];
|
||||
}
|
||||
}
|
||||
for( int i = 0; i <= l[u]; i++ )
|
||||
{
|
||||
elp[ u + 1 ][ i ] ^= elp[ u ][ i ];
|
||||
elp[ u ][ i ] = index_of[ elp[ u ][ i ] ]; /*convert old elp value to index*/
|
||||
}
|
||||
}
|
||||
|
||||
u_lu[ u + 1 ] = u - l[ u + 1 ];
|
||||
|
||||
/* form (u+1)th discrepancy */
|
||||
if( u < NN - KK ) /* no discrepancy computed on last iteration */
|
||||
{
|
||||
if ( s[ u + 1 ] != -1 )
|
||||
{
|
||||
d[ u + 1 ] = alpha_to[ s[ u + 1 ] ];
|
||||
}
|
||||
else
|
||||
{
|
||||
d[ u + 1 ] = 0;
|
||||
}
|
||||
for( int i = 1; i <= l[ u + 1 ]; i++ )
|
||||
{
|
||||
if( ( s[ u + 1 - i ] != -1 ) && ( elp[ u + 1 ][ i] != 0 ) )
|
||||
{
|
||||
d[ u + 1 ] ^= alpha_to[ ( s[ u + 1 - i ]
|
||||
+ index_of[ elp[ u + 1 ][ i ] ] ) % NN ];
|
||||
}
|
||||
}
|
||||
|
||||
d[ u + 1 ] = index_of[ d[ u + 1 ] ]; /* put d[u+1] into index form */
|
||||
}
|
||||
}
|
||||
while( ( u < NN - KK ) && ( l[ u + 1 ] <= TT) );
|
||||
|
||||
u++;
|
||||
|
||||
if( l[ u ] <= TT ) /* can correct error */
|
||||
{
|
||||
/* put elp into index form */
|
||||
for( int i = 0; i <= l[u]; i++ )
|
||||
{
|
||||
elp[ u ][ i ] = index_of[ elp[ u ][ i ] ];
|
||||
}
|
||||
|
||||
/* find roots of the error location polynomial */
|
||||
if (l[u] >= 0) System.arraycopy(elp[u], 1, reg, 1, l[u]);
|
||||
|
||||
count = 0;
|
||||
|
||||
for( int i = 1; i <= NN; i++ )
|
||||
{
|
||||
q = 1;
|
||||
|
||||
for( int j = 1; j <= l[u]; j++ )
|
||||
{
|
||||
if( reg[ j ] != -1 )
|
||||
{
|
||||
reg[ j ] = ( reg[ j ] + j ) % NN;
|
||||
q ^= alpha_to[ reg[ j ] ];
|
||||
};
|
||||
}
|
||||
|
||||
if( q == 0 ) /* store root and error location number indices */
|
||||
{
|
||||
root[ count ] = i;
|
||||
loc[ count ] = NN - i;
|
||||
count++;
|
||||
};
|
||||
};
|
||||
|
||||
if( count == l[ u ] ) /* no. roots = degree of elp hence <= tt errors */
|
||||
{
|
||||
/* form polynomial z(x) */
|
||||
for( int i = 1; i <= l[ u ]; i++ ) /* Z[0] = 1 always - do not need */
|
||||
{
|
||||
if( ( s[ i ] != -1 ) && ( elp[ u ][ i ] != -1 ) )
|
||||
{
|
||||
z[ i ] = alpha_to[ s[ i ] ] ^ alpha_to[ elp[ u ][ i ] ];
|
||||
}
|
||||
else if( ( s[ i ] != -1 ) && ( elp[ u ][ i ] == -1 ) )
|
||||
{
|
||||
z[ i ] = alpha_to[ s[ i ] ];
|
||||
}
|
||||
else if( ( s[ i ] == -1 ) && ( elp[ u ][ i ] != -1 ) )
|
||||
{
|
||||
z[ i ] = alpha_to[ elp[ u ][ i ] ];
|
||||
}
|
||||
else
|
||||
{
|
||||
z[ i ] = 0;
|
||||
}
|
||||
|
||||
for( int j = 1; j < i; j++ )
|
||||
{
|
||||
if( ( s[ j ] != -1 ) && ( elp[ u ][ i - j ] != -1 ) )
|
||||
{
|
||||
z[ i ] ^= alpha_to[ ( elp[ u ][ i - j ] + s[ j ] ) % NN ];
|
||||
}
|
||||
}
|
||||
|
||||
z[ i ] = index_of[ z[ i ] ]; /* put into index form */
|
||||
};
|
||||
|
||||
/* evaluate errors at locations given by error location numbers loc[i] */
|
||||
for( int i = 0; i < NN; i++ )
|
||||
{
|
||||
err[ i ] = 0;
|
||||
|
||||
if( output[ i ] != -1 ) /* convert recd[] to polynomial form */
|
||||
{
|
||||
output[ i ] = alpha_to[ output[ i ] ];
|
||||
}
|
||||
else
|
||||
{
|
||||
output[ i ] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for( int i = 0; i < l[ u ]; i++ ) /* compute numerator of error term first */
|
||||
{
|
||||
err[ loc[ i ] ] = 1; /* accounts for z[0] */
|
||||
|
||||
for( int j = 1; j <= l[ u ]; j++ )
|
||||
{
|
||||
if( z[ j ] != -1 )
|
||||
{
|
||||
err[ loc[ i ] ] ^= alpha_to[ ( z[ j ] + j * root[ i ] ) % NN ];
|
||||
}
|
||||
}
|
||||
|
||||
if( err[ loc[ i ] ] != 0 )
|
||||
{
|
||||
err[ loc[ i ] ] = index_of[ err[ loc[ i ] ] ];
|
||||
|
||||
q = 0; /* form denominator of error term */
|
||||
|
||||
for (int j = 0; j < l[u]; j++)
|
||||
{
|
||||
if (j != i)
|
||||
{
|
||||
q += index_of[1 ^ alpha_to[(loc[j] + root[i]) % NN]];
|
||||
}
|
||||
}
|
||||
|
||||
q = q % NN;
|
||||
err[loc[i]] = alpha_to[(err[loc[i]] - q + NN) % NN];
|
||||
output[loc[i]] ^= err[loc[i]]; /*recd[i] must be in polynomial form */
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no. roots != degree of elp => >tt errors and cannot solve */
|
||||
irrecoverable_error = true;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
/* elp has degree >tt hence cannot solve */
|
||||
irrecoverable_error = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no non-zero syndromes => no errors: output received codeword */
|
||||
for (int i = 0; i < NN; i++)
|
||||
{
|
||||
if (output[i] != -1) /* convert recd[] to polynomial form */
|
||||
{
|
||||
output[i] = alpha_to[output[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
output[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( irrecoverable_error )
|
||||
{
|
||||
for (int i = 0; i < NN; i++) /* could return error flag if desired */
|
||||
{
|
||||
if (output[i] != -1) /* convert recd[] to polynomial form */
|
||||
{
|
||||
output[i] = alpha_to[output[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
output[i] = 0; /* just output received codeword as is */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return irrecoverable_error;
|
||||
}
|
||||
}
|
||||
290
src/main/java/io/github/dsheirer/edac/CRCDMR.java
Normal file
290
src/main/java/io/github/dsheirer/edac/CRCDMR.java
Normal file
@@ -0,0 +1,290 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This 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.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DMR CRC check/correction methods
|
||||
*/
|
||||
public class CRCDMR
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(CRCDMR.class);
|
||||
|
||||
/**
|
||||
* CRC-CCITT 16-bit checksums for a message length of 80 bits plus 16
|
||||
* additional checksums representing CRC checksum bit errors
|
||||
*
|
||||
* Generated by:
|
||||
* CRCUtil.generate( 80, 16, 0x11021l, 0xFFFFl, true );
|
||||
*/
|
||||
public static final int[] CCITT_80_CHECKSUMS = new int[]
|
||||
{
|
||||
0xE434, 0x721A, 0x390D, 0x9496, 0x4A4B, 0xAD35, 0xDE8A, 0x6F45, 0xBFB2,
|
||||
0x5FD9, 0xA7FC, 0x53FE, 0x29FF, 0x9CEF, 0xC667, 0xEB23, 0xFD81, 0xF6D0,
|
||||
0x7B68, 0x3DB4, 0x1EDA, 0xF6D, 0x8FA6, 0x47D3, 0xABF9, 0xDDEC, 0x6EF6,
|
||||
0x377B, 0x93AD, 0xC1C6, 0x60E3, 0xB861, 0xD420, 0x6A10, 0x3508, 0x1A84,
|
||||
0xD42, 0x6A1, 0x8B40, 0x45A0, 0x22D0, 0x1168, 0x8B4, 0x45A, 0x22D, 0x8906,
|
||||
0x4483, 0xAA51, 0xDD38, 0x6E9C, 0x374E, 0x1BA7, 0x85C3, 0xCAF1, 0xED68,
|
||||
0x76B4, 0x3B5A, 0x1DAD, 0x86C6, 0x4363, 0xA9A1, 0xDCC0, 0x6E60, 0x3730,
|
||||
0x1B98, 0xDCC, 0x6E6, 0x373, 0x89A9, 0xCCC4, 0x6662, 0x3331, 0x9188,
|
||||
0x48C4, 0x2462, 0x1231, 0x8108, 0x4084, 0x2042, 0x1021, 0x1, 0x2, 0x4, 0x8,
|
||||
0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000,
|
||||
0x8000,
|
||||
};
|
||||
|
||||
/**
|
||||
* Confirmed Packet Data Unit CRC-9 checksums, generated by:
|
||||
*
|
||||
* long[] table = CRCUtil.generate( 135, 9, 0x259l, 0x1FF, false, Parity.NONE );
|
||||
*/
|
||||
public static final int[] CRC9_CHECKSUMS = new int[]
|
||||
{
|
||||
0x1E7, 0x1F3, 0x1F9, 0x1FC, 0x0D2, 0x045, 0x122, 0x0BD, 0x15E, 0x083,
|
||||
0x141, 0x1A0, 0x0FC, 0x052, 0x005, 0x102, 0x0AD, 0x156, 0x087, 0x143,
|
||||
0x1A1, 0x1D0, 0x0C4, 0x04E, 0x00B, 0x105, 0x182, 0x0ED, 0x176, 0x097,
|
||||
0x14B, 0x1A5, 0x1D2, 0x0C5, 0x162, 0x09D, 0x14E, 0x08B, 0x145, 0x1A2,
|
||||
0x0FD, 0x17E, 0x093, 0x149, 0x1A4, 0x0FE, 0x053, 0x129, 0x194, 0x0E6,
|
||||
0x05F, 0x12F, 0x197, 0x1CB, 0x1E5, 0x1F2, 0x0D5, 0x16A, 0x099, 0x14C,
|
||||
0x08A, 0x069, 0x134, 0x0B6, 0x077, 0x13B, 0x19D, 0x1CE, 0x0CB, 0x165,
|
||||
0x1B2, 0x0F5, 0x17A, 0x091, 0x148, 0x088, 0x068, 0x018, 0x020, 0x03C,
|
||||
0x032, 0x035, 0x11A, 0x0A1, 0x150, 0x084, 0x06E, 0x01B, 0x10D, 0x186,
|
||||
0x0EF, 0x177, 0x1BB, 0x1DD, 0x1EE, 0x0DB, 0x16D, 0x1B6, 0x0F7, 0x17B,
|
||||
0x1BD, 0x1DE, 0x0C3, 0x161, 0x1B0, 0x0F4, 0x056, 0x007, 0x103, 0x181,
|
||||
0x1C0, 0x0CC, 0x04A, 0x009, 0x104, 0x0AE, 0x07B, 0x13D, 0x19E, 0x0E3,
|
||||
0x171, 0x1B8, 0x0F0, 0x054, 0x006, 0x02F, 0x117, 0x18B, 0x1C5, 0x1E2,
|
||||
0x0DD, 0x16E, 0x09B, 0x14D, 0x1A6
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error detection and correction of single-bit errors for CCITT 16-bit CRC protected 80-bit messages.
|
||||
*/
|
||||
public static BinaryMessage correctCCITT80(BinaryMessage message, int messageStart, int crcStart)
|
||||
{
|
||||
int calculated = 0; //Starting value
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = message.nextSetBit(messageStart);
|
||||
i >= messageStart && i < crcStart;
|
||||
i = message.nextSetBit(i + 1))
|
||||
{
|
||||
calculated ^= CCITT_80_CHECKSUMS[i - messageStart];
|
||||
}
|
||||
|
||||
int checksum = getIntChecksum(message, crcStart, 16);
|
||||
|
||||
int residual = calculated ^ checksum;
|
||||
|
||||
if(residual == 0 || residual == 0xFFFF)
|
||||
{
|
||||
message.setCRC(CRC.PASSED);
|
||||
|
||||
return message;
|
||||
}
|
||||
else
|
||||
{
|
||||
int errorLocation = getBitError(residual, CCITT_80_CHECKSUMS);
|
||||
|
||||
if(errorLocation >= 0)
|
||||
{
|
||||
message.flip(errorLocation + messageStart);
|
||||
|
||||
message.setCRC(CRC.CORRECTED);
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
message.setCRC(CRC.FAILED_CRC);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the CRC checksum for the specified message
|
||||
* @param message to check
|
||||
* @param start of the message
|
||||
* @param length of the message (assumes the CRC checksum follows)
|
||||
* @param checksums to use in calculating the message checksum
|
||||
* @return calculated checksum
|
||||
*/
|
||||
public static int calculate(BinaryMessage message, int start, int length, long[] checksums)
|
||||
{
|
||||
int calculated = 0;
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = message.nextSetBit(start); i >= start && i < length; i = message.nextSetBit(i + 1))
|
||||
{
|
||||
calculated ^= checksums[i - start];
|
||||
}
|
||||
|
||||
return calculated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error detection and correction of single-bit errors for CCITT 16-bit CRC protected 80-bit messages.
|
||||
*/
|
||||
public static int correctCCITT80(CorrectedBinaryMessage message, int messageStart, int crcStart, int mask)
|
||||
{
|
||||
int calculated = mask; //Starting value
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = message.nextSetBit(messageStart); i >= messageStart && i < crcStart; i = message.nextSetBit(i + 1))
|
||||
{
|
||||
calculated ^= CCITT_80_CHECKSUMS[i - messageStart];
|
||||
}
|
||||
|
||||
int checksum = getIntChecksum(message, crcStart, 16);
|
||||
|
||||
int residual = calculated ^ checksum;
|
||||
|
||||
if(residual == 0 || residual == 0xFFFF)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int errorLocation = getBitError(residual, CCITT_80_CHECKSUMS);
|
||||
|
||||
if(errorLocation >= 0)
|
||||
{
|
||||
message.flip(errorLocation + messageStart);
|
||||
message.incrementCorrectedBitCount(1);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
//Message has at least 2 bit errors - ie uncorrectable
|
||||
message.incrementCorrectedBitCount(2);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error detection for CRC-9 protected Confirmed Packet Data blocks. These
|
||||
* data blocks have a slightly complicated structure because the checksum
|
||||
* is located between bits 7-15, within a 144 bit block. The checksums
|
||||
* were generated assuming that the message is contiguous from 0 - 134 bits.
|
||||
* No data correction is performed.
|
||||
*/
|
||||
public static CRC checkCRC9(BinaryMessage message, int messageStart)
|
||||
{
|
||||
int calculated = 0x0; //Initial fill of all ones
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = message.nextSetBit(messageStart);
|
||||
i >= messageStart && i < messageStart + 144;
|
||||
i = message.nextSetBit(i + 1))
|
||||
{
|
||||
/* message bits before the CRC */
|
||||
if(i < (messageStart + 7))
|
||||
{
|
||||
calculated ^= CRC9_CHECKSUMS[i - messageStart];
|
||||
}
|
||||
/* message bits after the CRC */
|
||||
else if(i > (messageStart + 15))
|
||||
{
|
||||
calculated ^= CRC9_CHECKSUMS[i - messageStart - 9];
|
||||
}
|
||||
}
|
||||
|
||||
int checksum = message.getInt(messageStart + 7, messageStart + 15);
|
||||
|
||||
int residual = calculated ^ checksum;
|
||||
|
||||
if(residual == 0 || residual == 0x1FF)
|
||||
{
|
||||
return CRC.PASSED;
|
||||
}
|
||||
|
||||
return CRC.FAILED_CRC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the value of the message checksum as a long
|
||||
*/
|
||||
public static long getLongChecksum(BinaryMessage message,
|
||||
int crcStart, int crcLength)
|
||||
{
|
||||
return message.getLong(crcStart, crcStart + crcLength - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the value of the message checksum as an integer
|
||||
*/
|
||||
public static int getIntChecksum(BinaryMessage message,
|
||||
int crcStart, int crcLength)
|
||||
{
|
||||
return message.getInt(crcStart, crcStart + crcLength - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies any single bit error position that matches the checksum error.
|
||||
*/
|
||||
public static int getBitError(long checksumError, long[] checksums)
|
||||
{
|
||||
for(int x = 0; x < checksums.length; x++)
|
||||
{
|
||||
if(checksums[x] == checksumError)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies any single bit error position that matches the checksum error.
|
||||
*/
|
||||
public static int getBitError(int checksumError, int[] checksums)
|
||||
{
|
||||
for(int x = 0; x < checksums.length; x++)
|
||||
{
|
||||
if(checksums[x] == checksumError)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int crc8(BinaryMessage bits, int len) {
|
||||
int crc=0;
|
||||
final int K = 8;
|
||||
final boolean[] poly = new boolean[]{true,false,false,false,false,false,true,true,true}; // crc8 poly
|
||||
boolean[] buf = new boolean[256];
|
||||
if (len+K > 256) {
|
||||
return 0;
|
||||
}
|
||||
for (int i=0; i<len; i++){
|
||||
buf[i] = bits.get(i);
|
||||
}
|
||||
for (int i=0; i<len; i++)
|
||||
if (buf[i])
|
||||
for (int j=0; j<K+1; j++)
|
||||
buf[i+j] ^= poly[j];
|
||||
for (int i=0; i<K; i++){
|
||||
crc = (crc << 1) + (buf[len + i] ? 1:0);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
*
|
||||
* This 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/>
|
||||
******************************************************************************/
|
||||
@@ -23,217 +23,219 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CRCUtil
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( CRCUtil.class );
|
||||
private final static Logger mLog = LoggerFactory.getLogger(CRCUtil.class);
|
||||
|
||||
public static long[] generate( int messageSize,
|
||||
int crcSize,
|
||||
long polynomial,
|
||||
long initialFill,
|
||||
boolean includeCRCBitErrors )
|
||||
{
|
||||
long[] crcTable = new long[ messageSize +
|
||||
( includeCRCBitErrors ? crcSize : 0 ) ];
|
||||
|
||||
int[] checksumIndexes = new int[ crcSize ];
|
||||
|
||||
for( int x = 0; x < crcSize; x++ )
|
||||
{
|
||||
checksumIndexes[ x ] = messageSize + x;
|
||||
}
|
||||
public static long[] generate(int messageSize,
|
||||
int crcSize,
|
||||
long polynomial,
|
||||
long initialFill,
|
||||
boolean includeCRCBitErrors)
|
||||
{
|
||||
long[] crcTable = new long[messageSize +
|
||||
(includeCRCBitErrors ? crcSize : 0)];
|
||||
|
||||
for( int x = 0; x < messageSize; x++ )
|
||||
{
|
||||
BinaryMessage message = new BinaryMessage( messageSize + crcSize );
|
||||
|
||||
message.load( messageSize, crcSize, initialFill );
|
||||
|
||||
message.set( x );
|
||||
|
||||
message = decode( message, 0, messageSize, polynomial, crcSize );
|
||||
|
||||
long checksum = message.getLong( checksumIndexes );
|
||||
|
||||
crcTable[ x ] = checksum;
|
||||
}
|
||||
|
||||
if( includeCRCBitErrors )
|
||||
{
|
||||
for( int x = 0; x < crcSize; x++ )
|
||||
{
|
||||
crcTable[ messageSize + x ] = Long.rotateLeft( 1, x );
|
||||
}
|
||||
}
|
||||
|
||||
return crcTable;
|
||||
}
|
||||
int[] checksumIndexes = new int[crcSize];
|
||||
|
||||
/**
|
||||
* Generates the checksum table and treats the final bit of each check value
|
||||
* as a parity bit that complies with the request parity argument.
|
||||
*/
|
||||
public static long[] generate( int messageSize,
|
||||
int crcSize,
|
||||
long polynomial,
|
||||
long initialFill,
|
||||
boolean includeCRCBitErrors,
|
||||
Parity parity )
|
||||
{
|
||||
long[] table = generate( messageSize, crcSize, polynomial, initialFill,
|
||||
includeCRCBitErrors );
|
||||
for(int x = 0; x < crcSize; x++)
|
||||
{
|
||||
checksumIndexes[x] = messageSize + x;
|
||||
}
|
||||
|
||||
if( parity == Parity.NONE )
|
||||
{
|
||||
return table;
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int x = 0; x < table.length; x++ )
|
||||
{
|
||||
table[ x ] <<= 1;
|
||||
|
||||
if( parity( table[ x ] ) != parity )
|
||||
{
|
||||
table[ x ] ^= 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Parity{ EVEN, ODD, NONE };
|
||||
for(int x = 0; x < messageSize; x++)
|
||||
{
|
||||
BinaryMessage message = new BinaryMessage(messageSize + crcSize);
|
||||
|
||||
/* Determines the parity (number of 1 bits) for the value */
|
||||
public static Parity parity( long value )
|
||||
{
|
||||
return Long.bitCount( value ) % 2 == 0 ? Parity.EVEN : Parity.ODD;
|
||||
}
|
||||
|
||||
message.load(messageSize, crcSize, initialFill);
|
||||
|
||||
/**
|
||||
* Performs binary division of the polynomial into the message for bits
|
||||
* in index 0 to index messageLength - 1, leaving the results in the
|
||||
* checksum filed following the message.
|
||||
*
|
||||
* IN: MMMMMMMMMMMMMMMMCCCCCCCCCCC
|
||||
* OUT: 0000000000000000RRRRRRRRRRR
|
||||
*
|
||||
* M: Message
|
||||
* C: Checksum
|
||||
* R: Remainder
|
||||
*
|
||||
* @param message - message and crc
|
||||
* @param polynomial - crc polynomial - must be 1 bit longer than the crc width
|
||||
* @param messageSize - message length and start of the checksum
|
||||
*
|
||||
* @return message with all message bits zeroed out, and the remainder
|
||||
* placed in the crc field which starts at index messageLength
|
||||
*/
|
||||
public static BinaryMessage decode( BinaryMessage message,
|
||||
int messageStart,
|
||||
int messageSize,
|
||||
long polynomial,
|
||||
int crcSize )
|
||||
{
|
||||
for (int i = message.nextSetBit( messageStart );
|
||||
i >= messageStart && i < messageSize;
|
||||
i = message.nextSetBit( i+1 ) )
|
||||
{
|
||||
BinaryMessage polySet = new BinaryMessage( crcSize + i + 1 );
|
||||
|
||||
polySet.load( i, crcSize + 1, polynomial );
|
||||
|
||||
message.xor( polySet );
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static void main( String[] args )
|
||||
{
|
||||
mLog.debug( "Starting" );
|
||||
|
||||
// String raw = "000000001010001000000000100010100000000000000000010000000011001001011100000000000000000011111110001000101001011100101110000101000110011101101001";
|
||||
// BitSetBuffer message = BitSetBuffer.load( raw );
|
||||
//
|
||||
// long polynomial = 0x259l;
|
||||
//
|
||||
// decode( message, 0, 135, polynomial, 9 );
|
||||
//
|
||||
// mLog.debug( message.toString() );
|
||||
message.set(x);
|
||||
|
||||
long[] table = CRCUtil.generate( 135, 9, 0x259l, 0x1FF, false, Parity.NONE );
|
||||
mLog.debug( toCodeArray( table ) );
|
||||
mLog.debug( "Finished" );
|
||||
}
|
||||
|
||||
public static String toCodeArray( long[] values )
|
||||
{
|
||||
boolean integerArray = true;
|
||||
message = decode(message, 0, messageSize, polynomial, crcSize);
|
||||
|
||||
/* Determine the correct primitive type: int or long */
|
||||
for( long value: values )
|
||||
{
|
||||
if( value > Integer.MAX_VALUE || value < Integer.MIN_VALUE )
|
||||
{
|
||||
integerArray = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
long checksum = message.getLong(checksumIndexes);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
crcTable[x] = checksum;
|
||||
}
|
||||
|
||||
if( integerArray )
|
||||
{
|
||||
sb.append( "\npublic static final int[] CHECKSUMS = new int[]\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( "\npublic static final long[] CHECKSUMS = new long[]\n" );
|
||||
}
|
||||
sb.append( "{\n" );
|
||||
if(includeCRCBitErrors)
|
||||
{
|
||||
for(int x = 0; x < crcSize; x++)
|
||||
{
|
||||
crcTable[messageSize + x] = Long.rotateLeft(1, x);
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder row = new StringBuilder();
|
||||
row.append( " " );
|
||||
|
||||
for( long value: values )
|
||||
{
|
||||
StringBuilder element = new StringBuilder();
|
||||
|
||||
element.append( "0x" );
|
||||
element.append( Long.toHexString( value ).toUpperCase() );
|
||||
|
||||
if( !integerArray )
|
||||
{
|
||||
element.append( "l" );
|
||||
}
|
||||
|
||||
element.append( ", " );
|
||||
|
||||
if( row.length() + element.length() <= 80 )
|
||||
{
|
||||
row.append( element.toString() );
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append( row.toString() );
|
||||
sb.append( "\n" );
|
||||
row = new StringBuilder();
|
||||
row.append( " " );
|
||||
|
||||
row.append( element.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
if( row.length() > 4 )
|
||||
{
|
||||
sb.append( row.toString() );
|
||||
sb.append( "\n" );
|
||||
}
|
||||
|
||||
sb.append( "};\n" );
|
||||
return crcTable;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
/**
|
||||
* Generates the checksum table and treats the final bit of each check value
|
||||
* as a parity bit that complies with the request parity argument.
|
||||
*/
|
||||
public static long[] generate(int messageSize,
|
||||
int crcSize,
|
||||
long polynomial,
|
||||
long initialFill,
|
||||
boolean includeCRCBitErrors,
|
||||
Parity parity)
|
||||
{
|
||||
long[] table = generate(messageSize, crcSize, polynomial, initialFill,
|
||||
includeCRCBitErrors);
|
||||
|
||||
if(parity == Parity.NONE)
|
||||
{
|
||||
return table;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int x = 0; x < table.length; x++)
|
||||
{
|
||||
table[x] <<= 1;
|
||||
|
||||
if(parity(table[x]) != parity)
|
||||
{
|
||||
table[x] ^= 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Parity
|
||||
{EVEN, ODD, NONE}
|
||||
|
||||
;
|
||||
|
||||
/* Determines the parity (number of 1 bits) for the value */
|
||||
public static Parity parity(long value)
|
||||
{
|
||||
return Long.bitCount(value) % 2 == 0 ? Parity.EVEN : Parity.ODD;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Performs binary division of the polynomial into the message for bits
|
||||
* in index 0 to index messageLength - 1, leaving the results in the
|
||||
* checksum filed following the message.
|
||||
*
|
||||
* IN: MMMMMMMMMMMMMMMMCCCCCCCCCCC
|
||||
* OUT: 0000000000000000RRRRRRRRRRR
|
||||
*
|
||||
* M: Message
|
||||
* C: Checksum
|
||||
* R: Remainder
|
||||
*
|
||||
* @param message - message and crc
|
||||
* @param polynomial - crc polynomial - must be 1 bit longer than the crc width
|
||||
* @param messageSize - message length and start of the checksum
|
||||
* @return message with all message bits zeroed out, and the remainder
|
||||
* placed in the crc field which starts at index messageLength
|
||||
*/
|
||||
public static BinaryMessage decode(BinaryMessage message,
|
||||
int messageStart,
|
||||
int messageSize,
|
||||
long polynomial,
|
||||
int crcSize)
|
||||
{
|
||||
for(int i = message.nextSetBit(messageStart);
|
||||
i >= messageStart && i < messageSize;
|
||||
i = message.nextSetBit(i + 1))
|
||||
{
|
||||
BinaryMessage polySet = new BinaryMessage(crcSize + i + 1);
|
||||
|
||||
polySet.load(i, crcSize + 1, polynomial);
|
||||
|
||||
message.xor(polySet);
|
||||
System.out.println(message.toString());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public static String toCodeArray(long[] values)
|
||||
{
|
||||
boolean integerArray = true;
|
||||
|
||||
/* Determine the correct primitive type: int or long */
|
||||
for(long value : values)
|
||||
{
|
||||
if(value > Integer.MAX_VALUE || value < Integer.MIN_VALUE)
|
||||
{
|
||||
integerArray = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(integerArray)
|
||||
{
|
||||
sb.append("\npublic static final int[] CHECKSUMS = new int[]\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("\npublic static final long[] CHECKSUMS = new long[]\n");
|
||||
}
|
||||
sb.append("{\n");
|
||||
|
||||
StringBuilder row = new StringBuilder();
|
||||
row.append(" ");
|
||||
|
||||
for(long value : values)
|
||||
{
|
||||
StringBuilder element = new StringBuilder();
|
||||
|
||||
element.append("0x");
|
||||
element.append(Long.toHexString(value).toUpperCase());
|
||||
|
||||
if(!integerArray)
|
||||
{
|
||||
element.append("l");
|
||||
}
|
||||
|
||||
element.append(", ");
|
||||
|
||||
if(row.length() + element.length() <= 80)
|
||||
{
|
||||
row.append(element.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(row.toString());
|
||||
sb.append("\n");
|
||||
row = new StringBuilder();
|
||||
row.append(" ");
|
||||
|
||||
row.append(element.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if(row.length() > 4)
|
||||
{
|
||||
sb.append(row.toString());
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
sb.append("};\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.debug("Starting");
|
||||
|
||||
//DMR message
|
||||
// String raw = "100110010001000000001001000000001111011011010000011001010000000000000000000000001100001000001011";
|
||||
String raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101001001111010001110";
|
||||
mLog.debug(raw);
|
||||
raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101000000000000000000";
|
||||
BinaryMessage message = BinaryMessage.load(raw);
|
||||
mLog.debug(message.toString());
|
||||
|
||||
long polynomial = 0x11021l;
|
||||
decode(message, 0, 80, polynomial, 16);
|
||||
mLog.debug(message.toString());
|
||||
mLog.debug("Finished");
|
||||
}
|
||||
}
|
||||
|
||||
129
src/main/java/io/github/dsheirer/edac/Hamming13.java
Normal file
129
src/main/java/io/github/dsheirer/edac/Hamming13.java
Normal file
@@ -0,0 +1,129 @@
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
|
||||
/**
|
||||
* Implements Hamming(13,9,3) Error Detection algorithm
|
||||
*/
|
||||
public class Hamming13
|
||||
{
|
||||
//DMR Checksums from generator matrix TS 102 361-1 Table B.14
|
||||
private static int[] CHECKSUMS = new int[]{0xF, 0xE, 0x7, 0xA, 0x5, 0XB, 0xC, 0x6, 0x3};
|
||||
private static int[] ERROR_INDEX = new int[]{-1, 12, 11, 8, 10, 4, 7, 2, 9, -1, 3, 5, 6, -1, 1, 0, -1};
|
||||
|
||||
/**
|
||||
* Calculates the bit error index of the Hamming(13,9,3) protected word that is contained in the binary message
|
||||
* starting at the specified offset.
|
||||
*
|
||||
* @param message containing a Hamming protected word
|
||||
* @param offset to the start of the protected word
|
||||
* @return message index for an error bit or -1 if no errors are detected.
|
||||
*/
|
||||
public static int getErrorIndex(BinaryMessage message, int offset)
|
||||
{
|
||||
int syndrome = getSyndrome(message, offset);
|
||||
|
||||
if(syndrome > 0)
|
||||
{
|
||||
return offset + ERROR_INDEX[syndrome];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the bit error index of the Hamming(13,9,3) protected word that is contained in the binary message
|
||||
* at the specified indices.
|
||||
*
|
||||
* @param message containing the Hamming protected word
|
||||
* @param indices to the word
|
||||
* @return index of error bit or -1 if no errors are detected
|
||||
*/
|
||||
public static int getErrorIndex(BinaryMessage message, int[] indices)
|
||||
{
|
||||
int syndrome = getSyndrome(message, indices);
|
||||
|
||||
if(syndrome > 0)
|
||||
{
|
||||
int errorIndex = ERROR_INDEX[syndrome];
|
||||
|
||||
if(errorIndex >= 0 && errorIndex < indices.length)
|
||||
{
|
||||
return indices[errorIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 10000 + errorIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the parity checksum (Parity 8,4,2,1) for data (9 <> 1 ) bits.
|
||||
*
|
||||
* @param message containing Hamming(15) protected word
|
||||
* @param offset to the Hamming protected word
|
||||
* @return parity value, 0 - 15
|
||||
*/
|
||||
private static int calculateChecksum(BinaryMessage message, int offset)
|
||||
{
|
||||
int calculated = 0; //Starting value
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = message.nextSetBit(offset); i >= offset && i < offset + 9; i = message.nextSetBit(i + 1))
|
||||
{
|
||||
calculated ^= CHECKSUMS[i - offset];
|
||||
}
|
||||
|
||||
return calculated;
|
||||
}
|
||||
|
||||
private static int calculateChecksum(BinaryMessage message, int[] indices)
|
||||
{
|
||||
int calculated = 0; //Starting value
|
||||
|
||||
for(int x = 0; x < 9; x++)
|
||||
{
|
||||
if(message.get(indices[x]))
|
||||
{
|
||||
calculated ^= CHECKSUMS[x];
|
||||
}
|
||||
}
|
||||
|
||||
return calculated;
|
||||
}
|
||||
|
||||
private static int getSyndrome(BinaryMessage message, int[] indices)
|
||||
{
|
||||
int calculated = calculateChecksum(message, indices);
|
||||
|
||||
int checksum = 0;
|
||||
for(int x = 9; x < 13; x++)
|
||||
{
|
||||
checksum = Integer.rotateLeft(checksum, 1);
|
||||
|
||||
if(message.get(indices[x]))
|
||||
{
|
||||
checksum++;
|
||||
}
|
||||
}
|
||||
|
||||
return calculated ^ checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the syndrome as the xor of the calculated checksum and the actual checksum.
|
||||
*
|
||||
* @param message containing a hamming(15,11,3) protected word
|
||||
* @param offset to bit 0 of the hamming protected word
|
||||
* @return syndrome that can be used with the ERROR_INDEX error to find the index of the bit position error
|
||||
*/
|
||||
private static int getSyndrome(BinaryMessage message, int offset)
|
||||
{
|
||||
int calculated = calculateChecksum(message, offset);
|
||||
int checksum = message.getInt(offset + 9, offset + 12);
|
||||
return (checksum ^ calculated);
|
||||
}
|
||||
}
|
||||
@@ -2,116 +2,66 @@ package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
|
||||
/**
|
||||
* Implements Hamming(15,11,3) Error Detection algorithm
|
||||
*/
|
||||
public class Hamming15
|
||||
{
|
||||
private static int[] CHECKSUMS = new int[] { 0xF,0xE,0xD,0xC,0xB,0XA,0x9,
|
||||
0x7,0x6,0x5,0x3 };
|
||||
//DMR Checksums from generator matrix TS 102 361-1 Table B.15
|
||||
private static int[] CHECKSUMS = new int[]{0x9, 0xD, 0xF, 0xE, 0x7, 0XA, 0x5, 0xB, 0xC, 0x6, 0x3};
|
||||
private static int[] ERROR_INDEX = new int[]{-1, 14, 13, 10, 12, 6, 9, 4, 11, 0, 5, 7, 8, 1, 3, 2};
|
||||
|
||||
/**
|
||||
* Performs error detection and correction of any single-bit errors.
|
||||
*
|
||||
* @param frame - binary frame containing a hamming(15,11,4) protected field
|
||||
*
|
||||
* @param startIndex - offset to the first bit of the field
|
||||
*
|
||||
* @return - 0 = no errors
|
||||
* 1 = a single-bit error was corrected
|
||||
* 2 = two or more errors detected - no corrections made
|
||||
*/
|
||||
public static int checkAndCorrect( BinaryMessage frame, int startIndex )
|
||||
{
|
||||
int syndrome = getSyndrome( frame, startIndex );
|
||||
/**
|
||||
* Calculates the bit error index of the Hamming(15,11,3) protected word that is contained in the binary message
|
||||
* starting at the specified offset.
|
||||
*
|
||||
* @param message containing a Hamming protected word
|
||||
* @param offset to the start of the protected word
|
||||
* @return message index for an error bit or -1 if no errors are detected.
|
||||
*/
|
||||
public static int getErrorIndex(BinaryMessage message, int offset)
|
||||
{
|
||||
int syndrome = getSyndrome(message, offset);
|
||||
|
||||
switch( syndrome )
|
||||
{
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
frame.flip( startIndex + 14 ); //Parity 1
|
||||
return 1;
|
||||
case 2:
|
||||
frame.flip( startIndex + 13 ); //Parity 2
|
||||
return 1;
|
||||
case 3:
|
||||
frame.flip( startIndex + 10 ); //Data 1
|
||||
return 1;
|
||||
case 4:
|
||||
frame.flip( startIndex + 12 ); //Parity 4
|
||||
return 1;
|
||||
case 5:
|
||||
frame.flip( startIndex + 9 ); //Data 2
|
||||
return 1;
|
||||
case 6:
|
||||
frame.flip( startIndex + 8 ); //Data 3
|
||||
return 1;
|
||||
case 7:
|
||||
frame.flip( startIndex + 7 ); //Data 4
|
||||
return 1;
|
||||
case 8:
|
||||
frame.flip( startIndex + 11 ); //Parity 8
|
||||
return 1;
|
||||
case 9:
|
||||
frame.flip( startIndex + 6 ); //Data 5
|
||||
return 1;
|
||||
case 10:
|
||||
frame.flip( startIndex + 5 ); //Data 6
|
||||
return 1;
|
||||
case 11:
|
||||
frame.flip( startIndex + 4 ); //Data 7
|
||||
return 1;
|
||||
case 12:
|
||||
frame.flip( startIndex + 3 ); //Data 8
|
||||
return 1;
|
||||
case 13:
|
||||
frame.flip( startIndex + 2 ); //Data 9
|
||||
return 1;
|
||||
case 14:
|
||||
frame.flip( startIndex + 1 ); //Data 10
|
||||
return 1;
|
||||
case 15:
|
||||
frame.flip( startIndex ); //Data 11
|
||||
return 1;
|
||||
}
|
||||
if(syndrome > 0)
|
||||
{
|
||||
return offset + ERROR_INDEX[syndrome];
|
||||
}
|
||||
|
||||
/* We'll never get to here */
|
||||
return 2;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the checksum (Parity 8,4,2,1) for data (11 <> 1 ) bits.
|
||||
* @param frame - frame containing hamming(15) protected word
|
||||
* @param startIndex - start bit index of the hamming protected word
|
||||
* @return parity value, 0 - 15
|
||||
*/
|
||||
private static int calculateChecksum( BinaryMessage frame, int startIndex )
|
||||
{
|
||||
int calculated = 0; //Starting value
|
||||
/**
|
||||
* Calculates the parity checksum (Parity 8,4,2,1) for data (11 <> 1 ) bits.
|
||||
*
|
||||
* @param message containing Hamming(15) protected word
|
||||
* @param offset to the Hamming protected word
|
||||
* @return parity value, 0 - 15
|
||||
*/
|
||||
private static int calculateChecksum(BinaryMessage message, int offset)
|
||||
{
|
||||
int calculated = 0; //Starting value
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for (int i = frame.nextSetBit( startIndex );
|
||||
i >= startIndex && i < startIndex + 11;
|
||||
i = frame.nextSetBit( i+1 ) )
|
||||
{
|
||||
calculated ^= CHECKSUMS[ i - startIndex ];
|
||||
}
|
||||
|
||||
return calculated;
|
||||
}
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = message.nextSetBit(offset); i >= offset && i < offset + 11; i = message.nextSetBit(i + 1))
|
||||
{
|
||||
calculated ^= CHECKSUMS[i - offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the syndrome - xor of the calculated checksum and the actual
|
||||
* checksum.
|
||||
*
|
||||
* @param frame - binary frame containing a hamming(15,11,4) protected word
|
||||
* @param startIndex - of bit 0 of the hamming protected word
|
||||
* @return - 0 (no errors) or 1 (single bit error corrected)
|
||||
*/
|
||||
private static int getSyndrome( BinaryMessage frame, int startIndex )
|
||||
{
|
||||
int calculated = calculateChecksum( frame, startIndex );
|
||||
|
||||
int checksum = frame.getInt( startIndex + 11, startIndex + 14 );
|
||||
return calculated;
|
||||
}
|
||||
|
||||
return ( checksum ^ calculated );
|
||||
}
|
||||
/**
|
||||
* Calculates the syndrome as the xor of the calculated checksum and the actual checksum.
|
||||
*
|
||||
* @param message containing a hamming(15,11,3) protected word
|
||||
* @param offset to bit 0 of the hamming protected word
|
||||
* @return syndrome that can be used with the ERROR_INDEX error to find the index of the bit position error
|
||||
*/
|
||||
private static int getSyndrome(BinaryMessage message, int offset)
|
||||
{
|
||||
int calculated = calculateChecksum(message, offset);
|
||||
int checksum = message.getInt(offset + 11, offset + 14);
|
||||
return (checksum ^ calculated);
|
||||
}
|
||||
}
|
||||
|
||||
120
src/main/java/io/github/dsheirer/edac/Hamming16.java
Normal file
120
src/main/java/io/github/dsheirer/edac/Hamming16.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
|
||||
/**
|
||||
* Hamming(16,11,5) Error detection and correction utility.
|
||||
*/
|
||||
public class Hamming16
|
||||
{
|
||||
private static int[] CHECKSUMS = new int[]{0x13, 0x1A, 0x1F, 0x1C, 0x0E, 0x15, 0x0B, 0x16, 0x19, 0x0D, 0x07};
|
||||
|
||||
/**
|
||||
* Performs error detection and correction of any single-bit errors and detection of any double-bit errors (SECDED)
|
||||
*
|
||||
* @param frame - binary frame containing a hamming(16,11,5) protected field
|
||||
* @param startIndex - offset to the first bit of the field
|
||||
* @return - 0 = no errors
|
||||
* 1 = a single-bit error was detected and corrected
|
||||
* 2 = two or more errors detected - no corrections made
|
||||
*/
|
||||
public static int checkAndCorrect(BinaryMessage frame, int startIndex)
|
||||
{
|
||||
int syndrome = getSyndrome(frame, startIndex);
|
||||
|
||||
switch(syndrome)
|
||||
{
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
frame.flip(startIndex + 15); //Parity 1
|
||||
return 1;
|
||||
case 2:
|
||||
frame.flip(startIndex + 14); //Parity 2
|
||||
return 1;
|
||||
case 3:
|
||||
frame.flip(startIndex + 10); //Data 1
|
||||
return 1;
|
||||
case 4:
|
||||
frame.flip(startIndex + 13); //Parity 4
|
||||
return 1;
|
||||
case 5:
|
||||
frame.flip(startIndex + 9); //Data 2
|
||||
return 1;
|
||||
case 6:
|
||||
frame.flip(startIndex + 8); //Data 3
|
||||
return 1;
|
||||
case 7:
|
||||
frame.flip(startIndex + 7); //Data 4
|
||||
return 1;
|
||||
case 8:
|
||||
frame.flip(startIndex + 12); //Parity 8
|
||||
return 1;
|
||||
case 9:
|
||||
frame.flip(startIndex + 6); //Data 5
|
||||
return 1;
|
||||
case 10:
|
||||
frame.flip(startIndex + 5); //Data 6
|
||||
return 1;
|
||||
case 11:
|
||||
frame.flip(startIndex + 4); //Data 7
|
||||
return 1;
|
||||
case 12:
|
||||
frame.flip(startIndex + 3); //Data 8
|
||||
return 1;
|
||||
case 13:
|
||||
frame.flip(startIndex + 2); //Data 9
|
||||
return 1;
|
||||
case 14:
|
||||
frame.flip(startIndex + 1); //Data 10
|
||||
return 1;
|
||||
case 15:
|
||||
frame.flip(startIndex); //Data 11
|
||||
return 1;
|
||||
case 16:
|
||||
frame.flip(startIndex + 11); //Parity 16
|
||||
return 1;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the checksum (Parity 16,8,4,2,1) for data (11 <> 1 ) bits.
|
||||
*
|
||||
* @param frame - frame containing hamming protected word
|
||||
* @param startIndex - start bit index of the hamming protected word
|
||||
* @return parity value, 0 - 16
|
||||
*/
|
||||
private static int calculateChecksum(BinaryMessage frame, int startIndex)
|
||||
{
|
||||
int calculated = 0; //Starting value
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = frame.nextSetBit(startIndex);
|
||||
i >= startIndex && i < startIndex + 11;
|
||||
i = frame.nextSetBit(i + 1))
|
||||
{
|
||||
calculated ^= CHECKSUMS[i - startIndex];
|
||||
}
|
||||
|
||||
return calculated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the syndrome - xor of the calculated checksum and the actual
|
||||
* checksum.
|
||||
*
|
||||
* @param frame - binary frame containing a hamming(16,11,5) protected word
|
||||
* @param startIndex - of bit 0 of the hamming protected word
|
||||
* @return - 0 (no errors) or 1 (single bit error corrected)
|
||||
*/
|
||||
private static int getSyndrome(BinaryMessage frame, int startIndex)
|
||||
{
|
||||
int calculated = calculateChecksum(frame, startIndex);
|
||||
|
||||
int checksum = frame.getInt(startIndex + 11, startIndex + 15);
|
||||
|
||||
return (checksum ^ calculated);
|
||||
}
|
||||
}
|
||||
123
src/main/java/io/github/dsheirer/edac/Hamming17.java
Normal file
123
src/main/java/io/github/dsheirer/edac/Hamming17.java
Normal file
@@ -0,0 +1,123 @@
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
|
||||
/**
|
||||
* Hamming(17,12,3) Error detection and correction utility.
|
||||
*/
|
||||
public class Hamming17
|
||||
{
|
||||
private static int[] CHECKSUMS = new int[]{0x1B, 0x1F, 0x1D, 0x1C, 0x0E, 0x07, 0x11, 0x1A, 0x0D, 0x14, 0x0A, 0x05};
|
||||
|
||||
/**
|
||||
* Performs error detection and correction of any single-bit errors and detection of any double-bit errors (SECDED)
|
||||
*
|
||||
* @param frame - binary frame containing a hamming(17,12,5) protected field
|
||||
* @param startIndex - offset to the first bit of the field
|
||||
* @return - 0 = no errors
|
||||
* 1 = a single-bit error was detected and corrected
|
||||
* 2 = two or more errors detected - no corrections made
|
||||
*/
|
||||
public static int checkAndCorrect(BinaryMessage frame, int startIndex)
|
||||
{
|
||||
int syndrome = getSyndrome(frame, startIndex);
|
||||
|
||||
switch(syndrome)
|
||||
{
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
frame.flip(startIndex + 16); //Parity 1
|
||||
return 1;
|
||||
case 2:
|
||||
frame.flip(startIndex + 15); //Parity 2
|
||||
return 1;
|
||||
case 3:
|
||||
frame.flip(startIndex + 11); //Data 1
|
||||
return 1;
|
||||
case 4:
|
||||
frame.flip(startIndex + 14); //Parity 4
|
||||
return 1;
|
||||
case 5:
|
||||
frame.flip(startIndex + 10); //Data 2
|
||||
return 1;
|
||||
case 6:
|
||||
frame.flip(startIndex + 9); //Data 3
|
||||
return 1;
|
||||
case 7:
|
||||
frame.flip(startIndex + 8); //Data 4
|
||||
return 1;
|
||||
case 8:
|
||||
frame.flip(startIndex + 13); //Parity 8
|
||||
return 1;
|
||||
case 9:
|
||||
frame.flip(startIndex + 7); //Data 5
|
||||
return 1;
|
||||
case 10:
|
||||
frame.flip(startIndex + 6); //Data 6
|
||||
return 1;
|
||||
case 11:
|
||||
frame.flip(startIndex + 5); //Data 7
|
||||
return 1;
|
||||
case 12:
|
||||
frame.flip(startIndex + 4); //Data 8
|
||||
return 1;
|
||||
case 13:
|
||||
frame.flip(startIndex + 3); //Data 9
|
||||
return 1;
|
||||
case 14:
|
||||
frame.flip(startIndex + 2); //Data 10
|
||||
return 1;
|
||||
case 15:
|
||||
frame.flip(startIndex + 1); //Data 11
|
||||
return 1;
|
||||
case 16:
|
||||
frame.flip(startIndex + 12); //Parity 16
|
||||
return 1;
|
||||
case 17:
|
||||
frame.flip(startIndex); //Data 12
|
||||
return 1;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the checksum (Parity 16,8,4,2,1) for data (12 <> 1 ) bits.
|
||||
*
|
||||
* @param frame - frame containing hamming protected word
|
||||
* @param startIndex - start bit index of the hamming protected word
|
||||
* @return parity value, 0 - 16
|
||||
*/
|
||||
private static int calculateChecksum(BinaryMessage frame, int startIndex)
|
||||
{
|
||||
int calculated = 0; //Starting value
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = frame.nextSetBit(startIndex);
|
||||
i >= startIndex && i < startIndex + 12;
|
||||
i = frame.nextSetBit(i + 1))
|
||||
{
|
||||
calculated ^= CHECKSUMS[i - startIndex];
|
||||
}
|
||||
|
||||
return calculated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the syndrome - xor of the calculated checksum and the actual
|
||||
* checksum.
|
||||
*
|
||||
* @param frame - binary frame containing a hamming(17,12,5) protected word
|
||||
* @param startIndex - of bit 0 of the hamming protected word
|
||||
* @return - 0 (no errors) or 1 (single bit error corrected)
|
||||
*/
|
||||
private static int getSyndrome(BinaryMessage frame, int startIndex)
|
||||
{
|
||||
int calculated = calculateChecksum(frame, startIndex);
|
||||
|
||||
int checksum = frame.getInt(startIndex + 12, startIndex + 16);
|
||||
|
||||
return (checksum ^ calculated);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Berlecamp-Massey decoder for Reed Solomon RS(12,9,4) protected messages using DMR generator polynomial
|
||||
*/
|
||||
public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
||||
{
|
||||
private static final int[] FLC_CODEWORD_0 = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
private static final int[] FLC_CODEWORD_1 = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||
private static final int[] FLC_CODEWORD_2 = {16, 17, 18, 19, 20, 21, 22, 23};
|
||||
private static final int[] FLC_CODEWORD_3 = {24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int[] FLC_CODEWORD_4 = {32, 33, 34, 35, 36, 37, 38, 39};
|
||||
private static final int[] FLC_CODEWORD_5 = {40, 41, 42, 43, 44, 45, 46, 47};
|
||||
private static final int[] FLC_CODEWORD_6 = {48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] FLC_CODEWORD_7 = {56, 57, 58, 59, 60, 61, 62, 63};
|
||||
private static final int[] FLC_CODEWORD_8 = {64, 65, 66, 67, 68, 69, 70, 71};
|
||||
private static final int[] FLC_PARITY_0 = {72, 73, 74, 75, 76, 77, 78, 79};
|
||||
private static final int[] FLC_PARITY_1 = {80, 81, 82, 83, 84, 85, 86, 87};
|
||||
private static final int[] FLC_PARITY_2 = {88, 89, 90, 91, 92, 93, 94, 95};
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public ReedSolomon_12_9_4_DMR()
|
||||
{
|
||||
//Note: the RS(12,9,4) is a shortened form of RS(255,252,4) over GF(8)
|
||||
super(255, 252);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to correct the Full Link Control message returning true if there were zero or one correctable errors
|
||||
* and returning false if there were two or more errors detected.
|
||||
*
|
||||
* Note: if one bit error was detected and corrected, the corrected bit count is set to 1 in the message
|
||||
*
|
||||
* @param message to be corrected.
|
||||
* @param crcMask as defined in the TS 102-361-1 Table B.21
|
||||
* @return true if the message was corrected.
|
||||
*/
|
||||
public boolean correctFullLinkControl(CorrectedBinaryMessage message, int crcMask)
|
||||
{
|
||||
int[] input = new int[255];
|
||||
int[] output = new int[255];
|
||||
|
||||
input[0] = message.getInt(FLC_PARITY_2) ^ crcMask;
|
||||
input[1] = message.getInt(FLC_PARITY_1) ^ crcMask;
|
||||
input[2] = message.getInt(FLC_PARITY_0) ^ crcMask;
|
||||
input[3] = message.getInt(FLC_CODEWORD_8);
|
||||
input[4] = message.getInt(FLC_CODEWORD_7);
|
||||
input[5] = message.getInt(FLC_CODEWORD_6);
|
||||
input[6] = message.getInt(FLC_CODEWORD_5);
|
||||
input[7] = message.getInt(FLC_CODEWORD_4);
|
||||
input[8] = message.getInt(FLC_CODEWORD_3);
|
||||
input[9] = message.getInt(FLC_CODEWORD_2);
|
||||
input[10] = message.getInt(FLC_CODEWORD_1);
|
||||
input[11] = message.getInt(FLC_CODEWORD_0);
|
||||
|
||||
try
|
||||
{
|
||||
boolean irrecoverableErrors = decode(input, output);
|
||||
|
||||
if(irrecoverableErrors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int pointer = 0;
|
||||
|
||||
for(int x = 11; x >= 0; x--)
|
||||
{
|
||||
if(output[x] != -1 && (output[x] != input[x]))
|
||||
{
|
||||
message.load(pointer, 8, output[x]);
|
||||
message.setCorrectedBitCount(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
pointer += 8;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -20,21 +20,18 @@
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
public class ReedSolomon_63_47_17 extends BerlekempMassey_63
|
||||
/**
|
||||
* Reed Solomon RS(24,12,13) decoder over GF(6)
|
||||
*/
|
||||
public class ReedSolomon_24_12_13_P25 extends ReedSolomon_63_P25
|
||||
{
|
||||
/**
|
||||
* Reed-Solomon RS(63,47,17) decoder. This can also be used for error detection
|
||||
* and correction of the following RS codes:
|
||||
* Constructs an instance.
|
||||
*
|
||||
* RS(36,20,17) - max 8 errors
|
||||
* RS(24,16,9) - max 4 errors
|
||||
* RS(24,12,13) - max 6 errors
|
||||
*
|
||||
* The maximum correctable errors is determined by (n-k)/2, or hamming
|
||||
* distance divided by 2.
|
||||
* Note: this is a shortened form of RS(63,51,13)
|
||||
*/
|
||||
public ReedSolomon_63_47_17(int maximumCorrectableErrors)
|
||||
public ReedSolomon_24_12_13_P25()
|
||||
{
|
||||
super(maximumCorrectableErrors);
|
||||
super(63,51);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This 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.edac;
|
||||
|
||||
/**
|
||||
* Reed Solomon RS(24,16,9) decoder over GF(6)
|
||||
*/
|
||||
public class ReedSolomon_24_16_9_P25 extends ReedSolomon_63_P25
|
||||
{
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* Note: this is a shortened form of RS(63,55,9)
|
||||
*/
|
||||
public ReedSolomon_24_16_9_P25()
|
||||
{
|
||||
super(63,55);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
/**
|
||||
* Berlekemp Massey decoder for GF(8) using DMR Generator Polynomial
|
||||
*/
|
||||
public class ReedSolomon_255_DMR extends BerlekempMassey
|
||||
{
|
||||
/**
|
||||
* DMR Reed-Solomon code is generated from a Galois Field(2^8) by the polynomial: a8 + a4 + a3 + a2 + a0.
|
||||
* In binary, this is expressed as: 100011101 which is reversed to big-endian format for this algorithm
|
||||
* See: TS 102-361-1 paragraph B.3.6 Reed-Solomon(12,9)
|
||||
*/
|
||||
public static final int[] DMR_GENERATOR_POLYNOMIAL = {1, 0, 1, 1, 1, 0, 0, 0, 1};
|
||||
|
||||
/**
|
||||
* Constructs an instance of RS(total symbols, message symbols)
|
||||
*
|
||||
* @param n total symbols
|
||||
* @param k message symbols
|
||||
*/
|
||||
public ReedSolomon_255_DMR(int n, int k)
|
||||
{
|
||||
super(8, n, k, DMR_GENERATOR_POLYNOMIAL);
|
||||
}
|
||||
}
|
||||
@@ -22,21 +22,17 @@
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
public class ReedSolomon_63_35_29 extends BerlekempMassey_63
|
||||
/**
|
||||
* Reed Solomon Decoder for P25 RS(44,16,29) protected messages.
|
||||
*/
|
||||
public class ReedSolomon_44_16_29_P25 extends ReedSolomon_63_P25
|
||||
{
|
||||
/**
|
||||
* Reed-Solomon RS(63,35,29) decoder. This can also be used for error detection and correction of the following
|
||||
* RS codes:
|
||||
*
|
||||
* RS(46,26,21) - max 10 errors = P25P2 IEMI
|
||||
* RS(45,26,20) - max 9 errors = P25P2 SOEMI (FACCH)
|
||||
* RS(52,30,23) - max 11 errors = P25P2 IOEMI (SACCH)
|
||||
* RS(44,16,29) - max 14 errors = P25P2 ESS
|
||||
*
|
||||
* The maximum correctable errors is determined by (n-k)/2, or hamming distance divided by 2.
|
||||
* Constructs an instance
|
||||
*/
|
||||
public ReedSolomon_63_35_29(int maximumCorrectableErrors)
|
||||
public ReedSolomon_44_16_29_P25()
|
||||
{
|
||||
super(maximumCorrectableErrors);
|
||||
//The RS(44,16,29) is shortened from RS(63,35,29)
|
||||
super(63,35);
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,16 @@
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
public class ReedSolomon_44_16_29 extends BerlekempMassey_63
|
||||
/**
|
||||
* Reed Solomon decoder for APCO-25 RS(63,35,29) over GF(6)
|
||||
*/
|
||||
public class ReedSolomon_63_35_29_P25 extends ReedSolomon_63_P25
|
||||
{
|
||||
//Hamming distance = 29, so max correctable errors = 29 / 2 = 14
|
||||
private static final int MAXIMUM_CORRECTABLE_ERRORS = 14;
|
||||
|
||||
public ReedSolomon_44_16_29()
|
||||
/**
|
||||
* Reed-Solomon RS(63,35,29) decoder.
|
||||
*/
|
||||
public ReedSolomon_63_35_29_P25()
|
||||
{
|
||||
super(MAXIMUM_CORRECTABLE_ERRORS);
|
||||
super(63, 35);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
*
|
||||
* This 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.edac;
|
||||
|
||||
public class ReedSolomon_63_47_17_P25 extends ReedSolomon_63_P25
|
||||
{
|
||||
/**
|
||||
* Reed-Solomon RS(63,47,17) decoder.
|
||||
*/
|
||||
public ReedSolomon_63_47_17_P25()
|
||||
{
|
||||
super(63,47);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
/**
|
||||
* Berlekemp Massey decoder using APCO-25 Generator Polynomial over GF(6)
|
||||
*/
|
||||
public class ReedSolomon_63_P25 extends BerlekempMassey
|
||||
{
|
||||
/**
|
||||
* APCO-25 Reed-Solomon code is generated from a Galois Field(2^6) by the polynomial: a6 + a1 + a0.
|
||||
* In binary, this is expressed as: 1000011 which is reversed to big-endian format for this algorithm
|
||||
* See: TIA 102-BAAA paragraph 5.9 Reed-Solomon Code Generator Matrices
|
||||
*/
|
||||
public static final int[] P25_GENERATOR_POLYNOMIAL = { 1, 1, 0, 0, 0, 0, 1 };
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param n total symbols
|
||||
* @param k message symbols
|
||||
*/
|
||||
public ReedSolomon_63_P25(int n, int k)
|
||||
{
|
||||
super(6, n, k, P25_GENERATOR_POLYNOMIAL);
|
||||
}
|
||||
}
|
||||
@@ -83,4 +83,10 @@ public class DMR_3_4_Node extends Node
|
||||
{
|
||||
return DMR_TRANSITION_MATRIX[precedingNode.getInputValue()][getInputValue()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "IN:" + getInputValue() + " OUT:" + getTransmittedOutputValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.edac.trellis;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -128,4 +130,10 @@ public class Path
|
||||
{
|
||||
return new Path(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "ERR:" + getError() + " " + Joiner.on(",").join(mNodes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,21 +18,77 @@
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.edac.trellis;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Viterbi decoder for 3/4 rate trellis coded modulation (TCM) data blocks
|
||||
*/
|
||||
public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder
|
||||
{
|
||||
private static final int[] DEINTERLEAVE_DIBITS = new int[]{0, 1, 8, 9, 16, 17, 24, 25, 32, 33, 40, 41, 48, 49, 56, 57, 64, 65, 72, 73, 80, 81,
|
||||
88, 89, 96, 97, 2, 3, 10, 11, 18, 19, 26, 27, 34, 35, 42, 43, 50, 51, 58, 59, 66, 67, 74, 75, 82, 83, 90, 91, 4, 5, 12, 13, 20, 21, 28, 29, 36, 37, 44, 45,
|
||||
52, 53, 60, 61, 68, 69, 76, 77, 84, 85, 92, 93, 6, 7, 14, 15, 22, 23, 30, 31, 38, 39, 46, 47, 54, 55, 62, 63, 70, 71, 78, 79, 86, 87, 94, 95};
|
||||
|
||||
private static final int[] DEINTERLEAVE_INDEXES = new int[]{0, 1, 2, 3, 16, 17, 18, 19, 32, 33, 34, 35, 48, 49, 50,
|
||||
51, 64, 65, 66, 67, 80, 81, 82, 83, 96, 97, 98, 99, 112, 113, 114, 115, 128, 129, 130, 131, 144, 145, 146, 147,
|
||||
160, 161, 162, 163, 176, 177, 178, 179, 192, 193, 194, 195, 4, 5, 6, 7, 20, 21, 22, 23, 36, 37, 38, 39, 52, 53,
|
||||
54, 55, 68, 69, 70, 71, 84, 85, 86, 87, 100, 101, 102, 103, 116, 117, 118, 119, 132, 133, 134, 135, 148, 149,
|
||||
150, 151, 164, 165, 166, 167, 180, 181, 182, 183, 8, 9, 10, 11, 24, 25, 26, 27, 40, 41, 42, 43, 56, 57, 58, 59,
|
||||
72, 73, 74, 75, 88, 89, 90, 91, 104, 105, 106, 107, 120, 121, 122, 123, 136, 137, 138, 139, 152, 153, 154, 155,
|
||||
168, 169, 170, 171, 184, 185, 186, 187, 12, 13, 14, 15, 28, 29, 30, 31, 44, 45, 46, 47, 60, 61, 62, 63, 76, 77,
|
||||
78, 79, 92, 93, 94, 95, 108, 109, 110, 111, 124, 125, 126, 127, 140, 141, 142, 143, 156, 157, 158, 159, 172,
|
||||
173, 174, 175, 188, 189, 190, 191};
|
||||
|
||||
/**
|
||||
* Viterbi decoder for Digital Mobile Radio (DMR) 3/4 rate Trellis Coded Modulation (TCM) encoded messages.
|
||||
*/
|
||||
public ViterbiDecoder_3_4_DMR()
|
||||
{
|
||||
super(3,4);
|
||||
super(3, 4);
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
List<Integer> indexes = new ArrayList<>();
|
||||
for(int i : DEINTERLEAVE_DIBITS)
|
||||
{
|
||||
indexes.add(i * 2);
|
||||
indexes.add(i * 2 + 1);
|
||||
}
|
||||
|
||||
System.out.println(Joiner.on(",").join(indexes));
|
||||
|
||||
Collections.sort(indexes);
|
||||
System.out.println(Joiner.on(",").join(indexes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs deinterleave of transmitted 3/4 TCM encoded payload
|
||||
* @param interleaved
|
||||
* @return
|
||||
*/
|
||||
private static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage interleaved)
|
||||
{
|
||||
CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(196);
|
||||
|
||||
for(int x = 0; x < 196; x++)
|
||||
{
|
||||
if(interleaved.get(x))
|
||||
{
|
||||
deinterleaved.set(DEINTERLEAVE_INDEXES[x]);
|
||||
}
|
||||
}
|
||||
|
||||
return deinterleaved;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes a 3/4 rate trellis coded modulation (TCM) encoded DMR binary message containing 196 bits that have
|
||||
* already been deinterleaved.
|
||||
@@ -40,12 +96,11 @@ public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder
|
||||
* @param encodedMessage to decode that has already been deinterleaved.
|
||||
* @return decoded message
|
||||
*/
|
||||
public CorrectedBinaryMessage decode(BinaryMessage encodedMessage)
|
||||
public CorrectedBinaryMessage decode(CorrectedBinaryMessage encodedMessage)
|
||||
{
|
||||
int[] symbols = getSymbols(encodedMessage);
|
||||
|
||||
CorrectedBinaryMessage deinterleaved = deinterleave(encodedMessage);
|
||||
int[] symbols = getSymbols(deinterleaved);
|
||||
Path mostLikelyPath = decode(symbols);
|
||||
|
||||
return getMessage(mostLikelyPath);
|
||||
}
|
||||
|
||||
@@ -104,7 +159,7 @@ public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder
|
||||
//Ensure we have an integral number of transmitted symbols (nibbles) in the message
|
||||
if(encodedMessage.size() % getOutputBitLength() != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Encoded message must contain an integral number of 4-bit symbols -" +
|
||||
throw new IllegalArgumentException("Encoded message must contain an integral number of 4-bit symbols -" +
|
||||
" message size: " + encodedMessage.size());
|
||||
}
|
||||
|
||||
@@ -125,6 +180,7 @@ public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder
|
||||
|
||||
/**
|
||||
* Creates a DMR node
|
||||
*
|
||||
* @param inputValue for the specific trellis node
|
||||
* @param transmittedOutputValue the actual transmitted output value
|
||||
* @return created node
|
||||
@@ -154,6 +210,6 @@ public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder
|
||||
@Override
|
||||
protected Node createStartingNode()
|
||||
{
|
||||
return new DMR_3_4_Node(0,0);
|
||||
return new DMR_3_4_Node(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,11 @@ import com.google.common.eventbus.EventBus;
|
||||
*/
|
||||
public class MyEventBus
|
||||
{
|
||||
private static final EventBus EVENT_BUS = new EventBus();
|
||||
private static final EventBus GLOBAL_EVENT_BUS = new EventBus();
|
||||
|
||||
public static EventBus getEventBus()
|
||||
//TODO: this is primarily used by the Preference Service ... move this event bus to the Preference Service
|
||||
public static EventBus getGlobalEventBus()
|
||||
{
|
||||
return EVENT_BUS;
|
||||
return GLOBAL_EVENT_BUS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ public class FilterSet<T> implements IFilter<T>
|
||||
{
|
||||
mName = "Message Filter";
|
||||
}
|
||||
|
||||
public FilterSet(IFilter<T> filter)
|
||||
{
|
||||
addFilter(filter);
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
|
||||
@@ -120,7 +120,7 @@ public class JavaFxWindowManager extends Application
|
||||
private void setup()
|
||||
{
|
||||
//Register this class to receive events via each method annotated with @Subscribe
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
//Register JavaFX icon fonts
|
||||
IconFontFX.register(jiconfont.icons.font_awesome.FontAwesome.getIconFont());
|
||||
@@ -162,6 +162,8 @@ public class JavaFxWindowManager extends Application
|
||||
*/
|
||||
public void shutdown()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
|
||||
if(mChannelMapStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mChannelMapStage);
|
||||
|
||||
@@ -337,15 +337,15 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
JMenu viewMenu = new JMenu("View");
|
||||
|
||||
JMenuItem viewPlaylistItem = new JMenuItem("Playlist Editor");
|
||||
viewPlaylistItem.addActionListener(e -> MyEventBus.getEventBus().post(new ViewPlaylistRequest()));
|
||||
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
|
||||
viewMenu.add(viewPlaylistItem);
|
||||
|
||||
JMenuItem preferencesItem = new JMenuItem("User Preferences");
|
||||
preferencesItem.addActionListener(e -> MyEventBus.getEventBus().post(new ViewUserPreferenceEditorRequest()));
|
||||
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
|
||||
viewMenu.add(preferencesItem);
|
||||
|
||||
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
||||
settingsMenu.addActionListener(arg0 -> MyEventBus.getEventBus().post(new ViewIconManagerRequest()));
|
||||
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||
viewMenu.add(settingsMenu);
|
||||
|
||||
JMenuItem logFilesMenu = new JMenuItem("Logs & Recordings");
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRDecoderInstrumented;
|
||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DMRPane extends ComplexDecoderPane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRPane.class);
|
||||
|
||||
private HBox mSampleChartBox;
|
||||
private ComplexSampleLineChart mSampleLineChart;
|
||||
private EyeDiagramChart mEyeDiagramChart;
|
||||
private HBox mDecoderChartBox;
|
||||
private SymbolChart mSymbolChart;
|
||||
private PhaseLineChart mPLLPhaseErrorLineChart;
|
||||
private DoubleLineChart mPLLFrequencyLineChart;
|
||||
private DoubleLineChart mSamplesPerSymbolLineChart;
|
||||
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
|
||||
private DMRDecoderInstrumented mDecoder = new DMRDecoderInstrumented(new DecodeConfigDMR());
|
||||
|
||||
public DMRPane()
|
||||
{
|
||||
super(DecoderType.P25_PHASE1);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addListener(getDecoder());
|
||||
|
||||
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
|
||||
getDecoder().setComplexSymbolListener(getSymbolChart());
|
||||
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
|
||||
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
|
||||
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
|
||||
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
|
||||
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
|
||||
|
||||
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
|
||||
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
|
||||
|
||||
mDecoder.setMessageListener(new Listener<IMessage>()
|
||||
{
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
mLog.debug(message.toString());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mLog.debug("Configuring for sample rate: " + sampleRate);
|
||||
|
||||
mDecoder.setSampleRate(sampleRate);
|
||||
double samplesPerSymbol = sampleRate / 4800.0;
|
||||
|
||||
getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol);
|
||||
}
|
||||
|
||||
private DMRDecoderInstrumented getDecoder()
|
||||
{
|
||||
return mDecoder;
|
||||
}
|
||||
|
||||
private SymbolChart getSymbolChart()
|
||||
{
|
||||
if(mSymbolChart == null)
|
||||
{
|
||||
mSymbolChart = new SymbolChart(10);
|
||||
}
|
||||
|
||||
return mSymbolChart;
|
||||
}
|
||||
|
||||
private HBox getDecoderChartBox()
|
||||
{
|
||||
if(mDecoderChartBox == null)
|
||||
{
|
||||
mDecoderChartBox = new HBox();
|
||||
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
|
||||
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
|
||||
getPLLFrequencyLineChart());
|
||||
}
|
||||
|
||||
return mDecoderChartBox;
|
||||
}
|
||||
|
||||
private HBox getSampleChartBox()
|
||||
{
|
||||
if(mSampleChartBox == null)
|
||||
{
|
||||
mSampleChartBox = new HBox();
|
||||
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
|
||||
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
|
||||
getSamplesPerSymbolLineChart());
|
||||
}
|
||||
|
||||
return mSampleChartBox;
|
||||
}
|
||||
|
||||
private ComplexSampleLineChart getSampleLineChart()
|
||||
{
|
||||
if(mSampleLineChart == null)
|
||||
{
|
||||
mSampleLineChart = new ComplexSampleLineChart("Raw Samples", 100, 10);
|
||||
}
|
||||
|
||||
return mSampleLineChart;
|
||||
}
|
||||
|
||||
private EyeDiagramChart getEyeDiagramChart()
|
||||
{
|
||||
if(mEyeDiagramChart == null)
|
||||
{
|
||||
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
|
||||
}
|
||||
|
||||
return mEyeDiagramChart;
|
||||
}
|
||||
|
||||
private PhaseLineChart getPLLPhaseErrorLineChart()
|
||||
{
|
||||
if(mPLLPhaseErrorLineChart == null)
|
||||
{
|
||||
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
|
||||
}
|
||||
|
||||
return mPLLPhaseErrorLineChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getPLLFrequencyLineChart()
|
||||
{
|
||||
if(mPLLFrequencyLineChart == null)
|
||||
{
|
||||
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
|
||||
}
|
||||
|
||||
return mPLLFrequencyLineChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getSamplesPerSymbolLineChart()
|
||||
{
|
||||
if(mSamplesPerSymbolLineChart == null)
|
||||
{
|
||||
mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 9.5, 11.5, 0.1, 40);
|
||||
}
|
||||
|
||||
return mSamplesPerSymbolLineChart;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,8 @@ public class DecoderPaneFactory
|
||||
DecoderType.LTR_NET,
|
||||
DecoderType.MDC1200,
|
||||
DecoderType.MPT1327,
|
||||
DecoderType.TAIT_1200);
|
||||
DecoderType.TAIT_1200,
|
||||
DecoderType.DMR);
|
||||
|
||||
/**
|
||||
* Creates a decoder pane for the decoder type
|
||||
@@ -59,6 +60,8 @@ public class DecoderPaneFactory
|
||||
throw new IllegalArgumentException("Use the getP25P1DecoderPane() method for P25 decoder type");
|
||||
case P25_PHASE2:
|
||||
return new P25Phase2HDQPSKPane();
|
||||
case DMR:
|
||||
return new DMRPane();
|
||||
}
|
||||
|
||||
return getDefaultPane();
|
||||
|
||||
@@ -151,12 +151,12 @@ public class PlaylistEditor extends BorderPane
|
||||
|
||||
MenuItem iconManagerItem = new MenuItem("_Icon Manager");
|
||||
iconManagerItem.setAccelerator(new KeyCodeCombination(KeyCode.I, KeyCombination.ALT_ANY));
|
||||
iconManagerItem.setOnAction(event -> MyEventBus.getEventBus().post(new ViewIconManagerRequest()));
|
||||
iconManagerItem.setOnAction(event -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||
viewMenu.getItems().add(iconManagerItem);
|
||||
|
||||
MenuItem userPreferenceItem = new MenuItem("_User Preferences");
|
||||
userPreferenceItem.setAccelerator(new KeyCodeCombination(KeyCode.U, KeyCombination.ALT_ANY));
|
||||
userPreferenceItem.setOnAction(event -> MyEventBus.getEventBus()
|
||||
userPreferenceItem.setOnAction(event -> MyEventBus.getGlobalEventBus()
|
||||
.post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.TALKGROUP_FORMAT)));
|
||||
viewMenu.getItems().add(userPreferenceItem);
|
||||
|
||||
|
||||
@@ -422,7 +422,7 @@ public class AliasConfigurationEditor extends SplitPane
|
||||
mAliasTableView.setItems(getAliasSortedList());
|
||||
mAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
mAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Alias>)c -> {
|
||||
setAliases(mAliasTableView.getSelectionModel().getSelectedItems());
|
||||
Platform.runLater(() -> setAliases(mAliasTableView.getSelectionModel().getSelectedItems()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ public class AliasEditor extends TabPane
|
||||
{
|
||||
if(mAliasViewByIdentifierEditor == null)
|
||||
{
|
||||
mAliasViewByIdentifierEditor = new AliasViewByIdentifierEditor(mPlaylistManager);
|
||||
mAliasViewByIdentifierEditor = new AliasViewByIdentifierEditor(mPlaylistManager, getAliasIdentifierTab().selectedProperty());
|
||||
}
|
||||
|
||||
return mAliasViewByIdentifierEditor;
|
||||
|
||||
@@ -172,7 +172,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mPlaylistManager.getBroadcastModel().getConfiguredBroadcasts()
|
||||
.addListener((ListChangeListener<ConfiguredBroadcast>)c -> updateStreamViews());
|
||||
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
@@ -410,6 +410,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
private VBox getTitledPanesBox()
|
||||
@@ -659,7 +660,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewAliasIdentifierRequest(selected));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewAliasIdentifierRequest(selected));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -687,6 +688,12 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
p25Menu.getItems().add(new SeparatorMenuItem());
|
||||
p25Menu.getItems().add(new AddTonesItem("Audio Tones (Phase 2 Only)"));
|
||||
|
||||
Menu dmrMenu = new ProtocolMenu(Protocol.DMR);
|
||||
dmrMenu.getItems().add(new AddTalkgroupItem(Protocol.DMR));
|
||||
dmrMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.DMR));
|
||||
dmrMenu.getItems().add(new AddRadioIdItem(Protocol.DMR));
|
||||
dmrMenu.getItems().add(new AddRadioIdRangeItem(Protocol.DMR));
|
||||
|
||||
Menu fleetsyncMenu = new ProtocolMenu(Protocol.FLEETSYNC);
|
||||
fleetsyncMenu.getItems().add(new AddTalkgroupItem(Protocol.FLEETSYNC));
|
||||
fleetsyncMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.FLEETSYNC));
|
||||
@@ -715,8 +722,8 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
Menu lojackMenu = new ProtocolMenu(Protocol.LOJACK);
|
||||
lojackMenu.getItems().add(new AddLojackItem());
|
||||
|
||||
mAddIdentifierButton.getItems().addAll(p25Menu, fleetsyncMenu, ltrMenu, mdcMenu, mptMenu, passportMenu,
|
||||
taitMenu, new SeparatorMenuItem(), lojackMenu);
|
||||
mAddIdentifierButton.getItems().addAll(p25Menu, dmrMenu, fleetsyncMenu, ltrMenu, mdcMenu, mptMenu,
|
||||
passportMenu, taitMenu, new SeparatorMenuItem(), lojackMenu);
|
||||
}
|
||||
|
||||
return mAddIdentifierButton;
|
||||
|
||||
@@ -25,11 +25,12 @@ import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -63,15 +64,19 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
private TableView<AliasAndIdentifier> mAliasAndIdentifierTableView;
|
||||
private TableColumn<AliasAndIdentifier,String> mIdentifierColumn;
|
||||
private Button mViewAliasButton;
|
||||
private boolean aliasListInvalidated;
|
||||
|
||||
public AliasViewByIdentifierEditor(PlaylistManager playlistManager)
|
||||
public AliasViewByIdentifierEditor(PlaylistManager playlistManager, ReadOnlyBooleanProperty tabSelectedProperty)
|
||||
{
|
||||
aliasListInvalidated = false;
|
||||
mPlaylistManager = playlistManager;
|
||||
mPlaylistManager.getAliasModel().aliasList().addListener((ListChangeListener<Alias>)c -> {
|
||||
//Refresh the list any time that the aliases change since we're using a static snapshot
|
||||
AliasIDType selected = getAliasIDTypeComboBox().getSelectionModel().getSelectedItem();
|
||||
getAliasIDTypeComboBox().getSelectionModel().select(null);
|
||||
getAliasIDTypeComboBox().getSelectionModel().select(selected);
|
||||
//Note if aliases change since we're using a static snapshot
|
||||
mPlaylistManager.getAliasModel().aliasList().addListener((InvalidationListener)observable -> aliasListInvalidated = true);
|
||||
|
||||
//Only update the list when this tab is selected to avoid many slow updates in the background
|
||||
tabSelectedProperty.addListener(observable -> {
|
||||
if (aliasListInvalidated)
|
||||
updateList();
|
||||
});
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
@@ -131,6 +136,8 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
}
|
||||
|
||||
getAliasAndIdentifierTableView().getSortOrder().setAll(mIdentifierColumn);
|
||||
|
||||
aliasListInvalidated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +185,7 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewAliasRequest(selected.getAlias()));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewAliasRequest(selected.getAlias()));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -190,22 +197,22 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
{
|
||||
if(mAliasAndIdentifierTableView == null)
|
||||
{
|
||||
mAliasAndIdentifierTableView = new TableView(FXCollections.observableArrayList(AliasAndIdentifier.extractor()));
|
||||
mAliasAndIdentifierTableView = new TableView<>(FXCollections.observableArrayList(AliasAndIdentifier.extractor()));
|
||||
mAliasAndIdentifierTableView.setPlaceholder(new Label("No aliases or identifiers available"));
|
||||
mIdentifierColumn = new TableColumn<>("Identifier");
|
||||
mIdentifierColumn.setCellValueFactory(new PropertyValueFactory<>("identifier"));
|
||||
mIdentifierColumn.setPrefWidth(350);
|
||||
|
||||
TableColumn aliasColumn = new TableColumn();
|
||||
TableColumn<AliasAndIdentifier, String> aliasColumn = new TableColumn<>();
|
||||
aliasColumn.setText("Alias");
|
||||
aliasColumn.setCellValueFactory(new PropertyValueFactory<>("alias"));
|
||||
aliasColumn.setPrefWidth(200);
|
||||
|
||||
TableColumn groupColumn = new TableColumn();
|
||||
TableColumn<AliasAndIdentifier, String> groupColumn = new TableColumn<>();
|
||||
groupColumn.setText("Group");
|
||||
groupColumn.setCellValueFactory(new PropertyValueFactory<>("group"));
|
||||
groupColumn.setPrefWidth(200);
|
||||
|
||||
|
||||
mAliasAndIdentifierTableView.getColumns().addAll(mIdentifierColumn, aliasColumn, groupColumn);
|
||||
mAliasAndIdentifierTableView.setMaxHeight(Double.MAX_VALUE);
|
||||
|
||||
|
||||
@@ -237,6 +237,10 @@ public class RadioIdEditor extends IdentifierEditor<Radio>
|
||||
"Format: 0 - 16777215"));
|
||||
mRadioDetails.add(new RadioDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - FFFFFF"));
|
||||
mRadioDetails.add(new RadioDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - 16777215"));
|
||||
mRadioDetails.add(new RadioDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - FFFFFF"));
|
||||
mRadioDetails.add(new RadioDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
|
||||
"Format: 0 - 8388607"));
|
||||
mRadioDetails.add(new RadioDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),
|
||||
|
||||
@@ -239,6 +239,10 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
||||
"Format: 0 - 65535"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
||||
"Format: 0 - FFFF"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - 16,777,215"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - FFFFFF"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
||||
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
|
||||
|
||||
@@ -303,7 +303,7 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
|
||||
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
||||
details.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
||||
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
||||
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
||||
"Format: PPP-IIII = Prefix (0-127), Ident (1-8191)"));
|
||||
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
||||
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||
|
||||
@@ -138,7 +138,7 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
|
||||
@@ -358,7 +358,7 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
|
||||
if(optionalButtonType.isPresent() && optionalButtonType.get() == ButtonType.YES)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.JMBE_LIBRARY));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.JMBE_LIBRARY));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public class ChannelConfigurationEditorFactory
|
||||
{
|
||||
case AM:
|
||||
return new AMConfigurationEditor(playlistManager, userPreferences);
|
||||
case DMR:
|
||||
return new DMRConfigurationEditor(playlistManager, userPreferences);
|
||||
case NBFM:
|
||||
return new NBFMConfigurationEditor(playlistManager, userPreferences);
|
||||
case LTR_NET:
|
||||
|
||||
@@ -0,0 +1,755 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.playlist.channel;
|
||||
|
||||
import io.github.dsheirer.gui.control.IntegerTextField;
|
||||
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
|
||||
import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor;
|
||||
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
|
||||
import io.github.dsheirer.gui.playlist.source.FrequencyField;
|
||||
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.log.EventLogType;
|
||||
import io.github.dsheirer.module.log.config.EventLogConfiguration;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DMR channel configuration editor
|
||||
*/
|
||||
public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRConfigurationEditor.class);
|
||||
private TitledPane mDecoderPane;
|
||||
private TitledPane mEventLogPane;
|
||||
private TitledPane mRecordPane;
|
||||
private TitledPane mSourcePane;
|
||||
private SourceConfigurationEditor mSourceConfigurationEditor;
|
||||
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
||||
private RecordConfigurationEditor mRecordConfigurationEditor;
|
||||
private ToggleSwitch mIgnoreDataCallsButton;
|
||||
private ToggleSwitch mIgnoreCRCChecksumsButton;
|
||||
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
||||
private IntegerTextField mLogicalSlotNumberField;
|
||||
private FrequencyField mDownlinkFrequencyField;
|
||||
// private FrequencyField mUplinkFrequencyField;
|
||||
private Button mAddTimeslotFrequencyButton;
|
||||
private Button mDeleteTimeslotFrequencyButton;
|
||||
private Spinner<Integer> mChannelRotationDelaySpinner;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param playlistManager
|
||||
*/
|
||||
public DMRConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences)
|
||||
{
|
||||
super(playlistManager, userPreferences);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
getTitledPanesBox().getChildren().add(getRecordPane());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.DMR;
|
||||
}
|
||||
|
||||
private TitledPane getSourcePane()
|
||||
{
|
||||
if(mSourcePane == null)
|
||||
{
|
||||
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
|
||||
mSourcePane.setExpanded(true);
|
||||
}
|
||||
|
||||
return mSourcePane;
|
||||
}
|
||||
|
||||
private TitledPane getDecoderPane()
|
||||
{
|
||||
if(mDecoderPane == null)
|
||||
{
|
||||
mDecoderPane = new TitledPane();
|
||||
mDecoderPane.setText("Decoder: DMR");
|
||||
mDecoderPane.setExpanded(true);
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setPadding(new Insets(10,10,10,10));
|
||||
gridPane.setHgap(10);
|
||||
gridPane.setVgap(10);
|
||||
|
||||
int row = 0;
|
||||
|
||||
Label poolSizeLabel = new Label("Max Traffic Channels");
|
||||
GridPane.setHalignment(poolSizeLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(poolSizeLabel, 0, row);
|
||||
gridPane.getChildren().add(poolSizeLabel);
|
||||
|
||||
GridPane.setConstraints(getTrafficChannelPoolSizeSpinner(), 1, row);
|
||||
gridPane.getChildren().add(getTrafficChannelPoolSizeSpinner());
|
||||
|
||||
GridPane.setConstraints(getIgnoreDataCallsButton(), 2, row);
|
||||
gridPane.getChildren().add(getIgnoreDataCallsButton());
|
||||
|
||||
Label ignoreDataLabel = new Label("Ignore Data Calls");
|
||||
GridPane.setHalignment(ignoreDataLabel, HPos.LEFT);
|
||||
GridPane.setConstraints(ignoreDataLabel, 3, row);
|
||||
gridPane.getChildren().add(ignoreDataLabel);
|
||||
|
||||
GridPane.setConstraints(getIgnoreCRCChecksumsButton(), 4, row);
|
||||
gridPane.getChildren().add(getIgnoreCRCChecksumsButton());
|
||||
|
||||
Label ignoreCRCLabel = new Label("Ignore CRC Checksums (RAS)");
|
||||
GridPane.setHalignment(ignoreCRCLabel, HPos.LEFT);
|
||||
GridPane.setConstraints(ignoreCRCLabel, 5, row);
|
||||
gridPane.getChildren().add(ignoreCRCLabel);
|
||||
|
||||
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
|
||||
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
||||
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
||||
gridPane.getChildren().add(timeslotTableLabel);
|
||||
|
||||
GridPane.setConstraints(getTimeslotTable(), 0, ++row, 6, 3);
|
||||
gridPane.getChildren().add(getTimeslotTable());
|
||||
|
||||
VBox buttonsBox = new VBox();
|
||||
buttonsBox.setAlignment(Pos.CENTER);
|
||||
buttonsBox.setSpacing(10);
|
||||
buttonsBox.getChildren().addAll(getAddTimeslotFrequencyButton(), getDeleteTimeslotFrequencyButton());
|
||||
|
||||
GridPane.setConstraints(buttonsBox, 6, row, 1, 3);
|
||||
gridPane.getChildren().addAll(buttonsBox);
|
||||
|
||||
row += 3;
|
||||
|
||||
HBox editorBox = new HBox();
|
||||
editorBox.setAlignment(Pos.CENTER_LEFT);
|
||||
editorBox.setSpacing(5);
|
||||
|
||||
Label lsnLabel = new Label("LSN");
|
||||
editorBox.getChildren().addAll(lsnLabel,getLogicalSlotNumberField());
|
||||
|
||||
Label downlinkLabel = new Label("Frequency (MHz)");
|
||||
downlinkLabel.setPadding(new Insets(0,0,0,5));
|
||||
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
|
||||
|
||||
// Label uplinkLabel = new Label("Uplink");
|
||||
// uplinkLabel.setPadding(new Insets(0,0,0,5));
|
||||
// editorBox.getChildren().addAll(uplinkLabel,getUplinkFrequencyField());
|
||||
|
||||
GridPane.setConstraints(editorBox, 0, row, 4, 1);
|
||||
gridPane.getChildren().add(editorBox);
|
||||
|
||||
mDecoderPane.setContent(gridPane);
|
||||
}
|
||||
|
||||
return mDecoderPane;
|
||||
}
|
||||
|
||||
private TitledPane getEventLogPane()
|
||||
{
|
||||
if(mEventLogPane == null)
|
||||
{
|
||||
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
|
||||
mEventLogPane.setExpanded(false);
|
||||
}
|
||||
|
||||
return mEventLogPane;
|
||||
}
|
||||
|
||||
private TitledPane getRecordPane()
|
||||
{
|
||||
if(mRecordPane == null)
|
||||
{
|
||||
mRecordPane = new TitledPane();
|
||||
mRecordPane.setText("Recording");
|
||||
mRecordPane.setExpanded(false);
|
||||
|
||||
Label notice = new Label("Note: use aliases to control call audio recording");
|
||||
notice.setPadding(new Insets(10, 10, 0, 10));
|
||||
|
||||
VBox vBox = new VBox();
|
||||
vBox.getChildren().addAll(getRecordConfigurationEditor(), notice);
|
||||
|
||||
mRecordPane.setContent(vBox);
|
||||
}
|
||||
|
||||
return mRecordPane;
|
||||
}
|
||||
|
||||
private SourceConfigurationEditor getSourceConfigurationEditor()
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
|
||||
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
|
||||
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
|
||||
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mSourceConfigurationEditor;
|
||||
}
|
||||
|
||||
private EventLogConfigurationEditor getEventLogConfigurationEditor()
|
||||
{
|
||||
if(mEventLogConfigurationEditor == null)
|
||||
{
|
||||
List<EventLogType> types = new ArrayList<>();
|
||||
types.add(EventLogType.CALL_EVENT);
|
||||
types.add(EventLogType.DECODED_MESSAGE);
|
||||
types.add(EventLogType.TRAFFIC_CALL_EVENT);
|
||||
types.add(EventLogType.TRAFFIC_DECODED_MESSAGE);
|
||||
|
||||
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
|
||||
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
|
||||
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mEventLogConfigurationEditor;
|
||||
}
|
||||
|
||||
private TableView<TimeslotFrequency> getTimeslotTable()
|
||||
{
|
||||
if(mTimeslotFrequencyTable == null)
|
||||
{
|
||||
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
|
||||
mTimeslotFrequencyTable.setPrefHeight(100.0);
|
||||
|
||||
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LSN");
|
||||
numberColumn.setPrefWidth(70);
|
||||
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
|
||||
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
|
||||
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
|
||||
|
||||
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
|
||||
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
|
||||
downlinkColumn.setPrefWidth(175);
|
||||
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
|
||||
|
||||
// TableColumn<TimeslotFrequency,Number> uplinkColumn = new TableColumn("Uplink (MHz)");
|
||||
// uplinkColumn.setCellValueFactory(cellData -> cellData.getValue().getUplinkMHz());
|
||||
// mTimeslotFrequencyTable.getColumns().addAll(uplinkColumn);
|
||||
|
||||
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
|
||||
}
|
||||
|
||||
return mTimeslotFrequencyTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified timeslot frequency into the editor
|
||||
*/
|
||||
private void setTimeslot(TimeslotFrequency timeslot)
|
||||
{
|
||||
//Preserve the current modified flag state since setting values in the editor will change it.
|
||||
boolean modified = modifiedProperty().get();
|
||||
|
||||
getLogicalSlotNumberField().setDisable(timeslot == null);
|
||||
getDownlinkFrequencyField().setDisable(timeslot == null);
|
||||
// getUplinkFrequencyField().setDisable(timeslot == null);
|
||||
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
|
||||
|
||||
if(timeslot != null)
|
||||
{
|
||||
getLogicalSlotNumberField().set(timeslot.getNumber());
|
||||
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
|
||||
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
|
||||
}
|
||||
else
|
||||
{
|
||||
getLogicalSlotNumberField().set(0);
|
||||
getDownlinkFrequencyField().set(0);
|
||||
// getUplinkFrequencyField().set(0);
|
||||
}
|
||||
|
||||
modifiedProperty().set(modified);
|
||||
}
|
||||
|
||||
private Button getAddTimeslotFrequencyButton()
|
||||
{
|
||||
if(mAddTimeslotFrequencyButton == null)
|
||||
{
|
||||
mAddTimeslotFrequencyButton = new Button("Add");
|
||||
mAddTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mAddTimeslotFrequencyButton.setOnAction(event -> addTimeslot());
|
||||
}
|
||||
|
||||
return mAddTimeslotFrequencyButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new timeslot frequency value and makes a best guess of the next sequential LSN number
|
||||
*/
|
||||
private void addTimeslot()
|
||||
{
|
||||
int lsn = 1;
|
||||
|
||||
while(hasLSN(lsn) && lsn <= 64) //64 is an arbitrary value to keep it from going too high
|
||||
{
|
||||
lsn++;
|
||||
}
|
||||
|
||||
TimeslotFrequency timeslotFrequency = new TimeslotFrequency();
|
||||
timeslotFrequency.setNumber(lsn);
|
||||
getTimeslotTable().getItems().add(timeslotFrequency);
|
||||
getTimeslotTable().scrollTo(timeslotFrequency);
|
||||
getTimeslotTable().getSelectionModel().select(timeslotFrequency);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the current timeslot frequency list to determine if the specified lsn is already listed
|
||||
*/
|
||||
private boolean hasLSN(int lsn)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: getTimeslotTable().getItems())
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == lsn)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Button getDeleteTimeslotFrequencyButton()
|
||||
{
|
||||
if(mDeleteTimeslotFrequencyButton == null)
|
||||
{
|
||||
mDeleteTimeslotFrequencyButton = new Button("Delete");
|
||||
mDeleteTimeslotFrequencyButton.setDisable(true);
|
||||
mDeleteTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mDeleteTimeslotFrequencyButton.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
getTimeslotTable().getItems().remove(selected);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDeleteTimeslotFrequencyButton;
|
||||
}
|
||||
|
||||
private IntegerTextField getLogicalSlotNumberField()
|
||||
{
|
||||
if(mLogicalSlotNumberField == null)
|
||||
{
|
||||
mLogicalSlotNumberField = new IntegerTextField();
|
||||
mLogicalSlotNumberField.setDisable(true);
|
||||
mLogicalSlotNumberField.setPrefWidth(65);
|
||||
mLogicalSlotNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
Integer value = mLogicalSlotNumberField.get();
|
||||
|
||||
if(value != null)
|
||||
{
|
||||
selected.setNumber(value);
|
||||
}
|
||||
}
|
||||
|
||||
modifiedProperty().set(true);
|
||||
});
|
||||
}
|
||||
|
||||
return mLogicalSlotNumberField;
|
||||
}
|
||||
|
||||
private FrequencyField getDownlinkFrequencyField()
|
||||
{
|
||||
if(mDownlinkFrequencyField == null)
|
||||
{
|
||||
mDownlinkFrequencyField = new FrequencyField();
|
||||
mDownlinkFrequencyField.setDisable(true);
|
||||
mDownlinkFrequencyField.textProperty().addListener(new ChangeListener<String>()
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
|
||||
{
|
||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
selected.setDownlinkFrequency(mDownlinkFrequencyField.get());
|
||||
}
|
||||
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDownlinkFrequencyField;
|
||||
}
|
||||
|
||||
// private FrequencyField getUplinkFrequencyField()
|
||||
// {
|
||||
// if(mUplinkFrequencyField == null)
|
||||
// {
|
||||
// mUplinkFrequencyField = new FrequencyField();
|
||||
// mUplinkFrequencyField.setDisable(true);
|
||||
// mUplinkFrequencyField.textProperty().addListener(new ChangeListener<String>()
|
||||
// {
|
||||
// @Override
|
||||
// public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
|
||||
// {
|
||||
// TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
//
|
||||
// if(selected != null)
|
||||
// {
|
||||
// selected.setUplinkFrequency(mUplinkFrequencyField.get());
|
||||
// int lsn = selected.getNumber();
|
||||
// selected.setNumber(-1);
|
||||
// selected.setNumber(lsn);
|
||||
// }
|
||||
//
|
||||
// modifiedProperty().set(true);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return mUplinkFrequencyField;
|
||||
// }
|
||||
|
||||
private ToggleSwitch getIgnoreDataCallsButton()
|
||||
{
|
||||
if(mIgnoreDataCallsButton == null)
|
||||
{
|
||||
mIgnoreDataCallsButton = new ToggleSwitch();
|
||||
mIgnoreDataCallsButton.setDisable(true);
|
||||
mIgnoreDataCallsButton.selectedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mIgnoreDataCallsButton;
|
||||
}
|
||||
|
||||
private ToggleSwitch getIgnoreCRCChecksumsButton()
|
||||
{
|
||||
if(mIgnoreCRCChecksumsButton == null)
|
||||
{
|
||||
mIgnoreCRCChecksumsButton = new ToggleSwitch();
|
||||
mIgnoreCRCChecksumsButton.setDisable(true);
|
||||
mIgnoreCRCChecksumsButton.selectedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mIgnoreCRCChecksumsButton;
|
||||
}
|
||||
|
||||
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
|
||||
{
|
||||
if(mTrafficChannelPoolSizeSpinner == null)
|
||||
{
|
||||
mTrafficChannelPoolSizeSpinner = new Spinner();
|
||||
mTrafficChannelPoolSizeSpinner.setDisable(true);
|
||||
mTrafficChannelPoolSizeSpinner.setTooltip(
|
||||
new Tooltip("Maximum number of traffic channels that can be created by the decoder"));
|
||||
mTrafficChannelPoolSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 50);
|
||||
mTrafficChannelPoolSizeSpinner.setValueFactory(svf);
|
||||
mTrafficChannelPoolSizeSpinner.getValueFactory().valueProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mTrafficChannelPoolSizeSpinner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel rotation monitor delay value. This dictates how long the decoder will remain on each frequency before
|
||||
* rotating to the next frequency in the list
|
||||
* @return spinner
|
||||
*/
|
||||
private Spinner<Integer> getChannelRotationDelaySpinner()
|
||||
{
|
||||
if(mChannelRotationDelaySpinner == null)
|
||||
{
|
||||
mChannelRotationDelaySpinner = new Spinner();
|
||||
mChannelRotationDelaySpinner.setDisable(true);
|
||||
mChannelRotationDelaySpinner.setTooltip(
|
||||
new Tooltip("Delay on each frequency before rotating to next when seeking to next active channel frequency"));
|
||||
mChannelRotationDelaySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(200, 2000, 200, 50);
|
||||
mChannelRotationDelaySpinner.setValueFactory(svf);
|
||||
mChannelRotationDelaySpinner.getValueFactory().valueProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mChannelRotationDelaySpinner;
|
||||
}
|
||||
|
||||
private RecordConfigurationEditor getRecordConfigurationEditor()
|
||||
{
|
||||
if(mRecordConfigurationEditor == null)
|
||||
{
|
||||
List<RecorderType> types = new ArrayList<>();
|
||||
types.add(RecorderType.BASEBAND);
|
||||
types.add(RecorderType.DEMODULATED_BIT_STREAM);
|
||||
types.add(RecorderType.MBE_CALL_SEQUENCE);
|
||||
types.add(RecorderType.TRAFFIC_BASEBAND);
|
||||
types.add(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM);
|
||||
types.add(RecorderType.TRAFFIC_MBE_CALL_SEQUENCE);
|
||||
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
|
||||
mRecordConfigurationEditor.setDisable(true);
|
||||
mRecordConfigurationEditor.modifiedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mRecordConfigurationEditor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDecoderConfiguration(DecodeConfiguration config)
|
||||
{
|
||||
getIgnoreCRCChecksumsButton().setDisable(config == null);
|
||||
getIgnoreDataCallsButton().setDisable(config == null);
|
||||
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
|
||||
getTimeslotTable().getItems().clear();
|
||||
getTimeslotTable().setDisable(config == null);
|
||||
getAddTimeslotFrequencyButton().setDisable(config == null);
|
||||
getDeleteTimeslotFrequencyButton().setDisable(true);
|
||||
getLogicalSlotNumberField().set(0);
|
||||
getLogicalSlotNumberField().setDisable(true);
|
||||
getDownlinkFrequencyField().set(0);
|
||||
getDownlinkFrequencyField().setDisable(true);
|
||||
// getUplinkFrequencyField().set(0);
|
||||
// getUplinkFrequencyField().setDisable(true);
|
||||
getChannelRotationDelaySpinner().setDisable(config == null);
|
||||
|
||||
if(config instanceof DecodeConfigDMR)
|
||||
{
|
||||
DecodeConfigDMR decodeConfig = (DecodeConfigDMR)config;
|
||||
|
||||
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
||||
getIgnoreCRCChecksumsButton().setSelected(decodeConfig.getIgnoreCRCChecksums());
|
||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
|
||||
{
|
||||
getTimeslotTable().getItems().add(timeslotFrequency.copy());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getIgnoreCRCChecksumsButton().setSelected(false);
|
||||
getIgnoreDataCallsButton().setSelected(false);
|
||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
|
||||
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveDecoderConfiguration()
|
||||
{
|
||||
DecodeConfigDMR config;
|
||||
|
||||
if(getItem().getDecodeConfiguration() instanceof DecodeConfigDMR)
|
||||
{
|
||||
config = (DecodeConfigDMR)getItem().getDecodeConfiguration();
|
||||
}
|
||||
else
|
||||
{
|
||||
config = new DecodeConfigDMR();
|
||||
}
|
||||
|
||||
// config.setChannelRotationDelay(getChannelRotationDelaySpinner().getValue());
|
||||
config.setIgnoreCRCChecksums(getIgnoreCRCChecksumsButton().isSelected());
|
||||
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
||||
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
||||
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));
|
||||
getItem().setDecodeConfiguration(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setEventLogConfiguration(EventLogConfiguration config)
|
||||
{
|
||||
getEventLogConfigurationEditor().setItem(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveEventLogConfiguration()
|
||||
{
|
||||
getEventLogConfigurationEditor().save();
|
||||
|
||||
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
|
||||
{
|
||||
getItem().setEventLogConfiguration(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAuxDecoderConfiguration()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRecordConfiguration(RecordConfiguration config)
|
||||
{
|
||||
getRecordConfigurationEditor().setDisable(config == null);
|
||||
getRecordConfigurationEditor().setItem(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveRecordConfiguration()
|
||||
{
|
||||
getRecordConfigurationEditor().save();
|
||||
RecordConfiguration config = getRecordConfigurationEditor().getItem();
|
||||
getItem().setRecordConfiguration(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setSourceConfiguration(SourceConfiguration config)
|
||||
{
|
||||
getSourceConfigurationEditor().setSourceConfiguration(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveSourceConfiguration()
|
||||
{
|
||||
getSourceConfigurationEditor().save();
|
||||
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
|
||||
getItem().setSourceConfiguration(sourceConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel tuner channel source frequencies value factory
|
||||
*/
|
||||
public class FrequencyCellValueFactory implements Callback<TableColumn.CellDataFeatures<TimeslotFrequency, String>,
|
||||
ObservableValue<String>>
|
||||
{
|
||||
private SimpleStringProperty mFrequency = new SimpleStringProperty();
|
||||
private boolean mIsDownlink;
|
||||
|
||||
public FrequencyCellValueFactory(boolean isDownlink)
|
||||
{
|
||||
mIsDownlink = isDownlink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> call(TableColumn.CellDataFeatures<TimeslotFrequency, String> param)
|
||||
{
|
||||
if(param.getValue() != null)
|
||||
{
|
||||
long frequency = (mIsDownlink ? param.getValue().getDownlinkFrequency() : param.getValue().getUplinkFrequency());
|
||||
mFrequency.set(String.valueOf(frequency / 1E6));
|
||||
}
|
||||
else
|
||||
{
|
||||
mFrequency.set(null);
|
||||
}
|
||||
|
||||
return mFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
public class DownlinkPropertyValueFactory extends PropertyValueFactory<TimeslotFrequency,String>
|
||||
{
|
||||
private StringProperty mStringProperty = new SimpleStringProperty();
|
||||
|
||||
public DownlinkPropertyValueFactory()
|
||||
{
|
||||
super("downlinkFrequency");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> call(TableColumn.CellDataFeatures<TimeslotFrequency,String> param)
|
||||
{
|
||||
if(param.getValue() != null)
|
||||
{
|
||||
mStringProperty.set(String.valueOf(param.getValue().getDownlinkFrequency() / 1E6));
|
||||
}
|
||||
else
|
||||
{
|
||||
mStringProperty.setValue(null);
|
||||
}
|
||||
|
||||
return mStringProperty;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -210,7 +210,7 @@ public class LTRConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
|
||||
@@ -210,7 +210,7 @@ public class LTRNetConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
|
||||
@@ -195,7 +195,10 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), true);
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
|
||||
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
|
||||
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
|
||||
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
@@ -276,7 +279,7 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
|
||||
channelMapName = getChannelMapComboBox().getSelectionModel().getSelectedItem().getName();
|
||||
}
|
||||
|
||||
MyEventBus.getEventBus().post(new ViewChannelMapEditorRequest(channelMapName));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelMapEditorRequest(channelMapName));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
|
||||
@@ -191,7 +191,10 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), true);
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
|
||||
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
|
||||
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
|
||||
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
|
||||
@@ -179,7 +179,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
@@ -305,7 +305,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
||||
config.setAutoDetectScrambleParameters(false);
|
||||
int wacn = getWacnTextField().get();
|
||||
int system = getSystemTextField().get();
|
||||
int nac = getSystemTextField().get();
|
||||
int nac = getNacTextField().get();
|
||||
config.setScrambleParameters(new ScrambleParameters(wacn, system, nac));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -155,7 +155,7 @@ public class PassportConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
if(mSourceConfigurationEditor == null)
|
||||
{
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
|
||||
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel());
|
||||
|
||||
//Add a listener so that we can push change notifications up to this editor
|
||||
mSourceConfigurationEditor.modifiedProperty()
|
||||
|
||||
@@ -94,7 +94,7 @@ public class PlaylistManagerEditor extends HBox
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
//Register to receive preferences updates
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
setPadding(new Insets(5, 5, 5, 5));
|
||||
HBox.setHgrow(getPlaylistTableView(), Priority.ALWAYS);
|
||||
@@ -102,6 +102,11 @@ public class PlaylistManagerEditor extends HBox
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the path argument is the same as the current playlist path
|
||||
*/
|
||||
|
||||
@@ -376,7 +376,7 @@ public class FrequencyEditor extends VBox
|
||||
|
||||
if(getShowCreatedChannelCheckBox().selectedProperty().get())
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user