mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Compare commits
23 Commits
v0.5.0-bet
...
v0.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9abf64c6ab | ||
|
|
1702103f5d | ||
|
|
8f68a989b9 | ||
|
|
b51a0102ef | ||
|
|
e177c2c6b0 | ||
|
|
3567fdf51d | ||
|
|
323097ba65 | ||
|
|
33f2bab7ac | ||
|
|
c063cfb201 | ||
|
|
e99aa3d3b4 | ||
|
|
ec162ff84c | ||
|
|
f0db2865f2 | ||
|
|
f97b2150b9 | ||
|
|
8ce8aaddf9 | ||
|
|
e4a85a4ed7 | ||
|
|
f61a415b43 | ||
|
|
29ed3b4553 | ||
|
|
a87309e792 | ||
|
|
ddb5b5ef87 | ||
|
|
7a58bf85b6 | ||
|
|
8707eb2c88 | ||
|
|
08499ac73f | ||
|
|
70170ff70f |
@@ -28,7 +28,7 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.5.0-beta7'
|
version = '0.5.1'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -212,7 +212,9 @@ runtime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Additional modules to include with auto-detected modules.
|
//Additional modules to include with auto-detected modules.
|
||||||
modules = ['jdk.crypto.ec'] //jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
|
//jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
|
||||||
|
//jdk.accessibility is used with assistive technologies like screen readers
|
||||||
|
modules = ['jdk.crypto.ec','jdk.accessibility']
|
||||||
|
|
||||||
//Use auto-detected modules and 'add' any specified modules.
|
//Use auto-detected modules and 'add' any specified modules.
|
||||||
additive = true
|
additive = true
|
||||||
|
|||||||
@@ -25,19 +25,15 @@ import io.github.dsheirer.audio.convert.MP3FrameTools;
|
|||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
||||||
{
|
{
|
||||||
@@ -255,7 +251,6 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
|||||||
public boolean canConnect()
|
public boolean canConnect()
|
||||||
{
|
{
|
||||||
BroadcastState state = getBroadcastState();
|
BroadcastState state = getBroadcastState();
|
||||||
|
|
||||||
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,6 @@ import io.github.dsheirer.properties.SystemProperties;
|
|||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -48,6 +41,12 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
|
||||||
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
||||||
{
|
{
|
||||||
@@ -58,7 +57,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
|
|
||||||
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
||||||
|
|
||||||
public static final int COLUMN_SERVER_ICON = 0;
|
public static final int COLUMN_BROADCAST_SERVER_TYPE = 0;
|
||||||
public static final int COLUMN_STREAM_NAME = 1;
|
public static final int COLUMN_STREAM_NAME = 1;
|
||||||
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
||||||
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
||||||
@@ -67,7 +66,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
|
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
|
||||||
|
|
||||||
public static final String[] COLUMN_NAMES = new String[]
|
public static final String[] COLUMN_NAMES = new String[]
|
||||||
{"Streaming", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
||||||
|
|
||||||
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
||||||
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
||||||
@@ -575,15 +574,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
{
|
{
|
||||||
switch(columnIndex)
|
switch(columnIndex)
|
||||||
{
|
{
|
||||||
case COLUMN_SERVER_ICON:
|
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||||
String iconPath = configuredBroadcast.getBroadcastConfiguration()
|
return configuredBroadcast.getBroadcastServerType();
|
||||||
.getBroadcastServerType().getIconPath();
|
|
||||||
|
|
||||||
if(iconPath != null && mIconModel != null)
|
|
||||||
{
|
|
||||||
return mIconModel.getScaledIcon(iconPath, 14);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case COLUMN_STREAM_NAME:
|
case COLUMN_STREAM_NAME:
|
||||||
return configuredBroadcast.getBroadcastConfiguration().getName();
|
return configuredBroadcast.getBroadcastConfiguration().getName();
|
||||||
case COLUMN_BROADCASTER_STATUS:
|
case COLUMN_BROADCASTER_STATUS:
|
||||||
@@ -653,8 +645,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||||
case COLUMN_BROADCASTER_ERROR_COUNT:
|
case COLUMN_BROADCASTER_ERROR_COUNT:
|
||||||
return Integer.class;
|
return Integer.class;
|
||||||
case COLUMN_SERVER_ICON:
|
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||||
return ImageIcon.class;
|
return BroadcastServerType.class;
|
||||||
case COLUMN_STREAM_NAME:
|
case COLUMN_STREAM_NAME:
|
||||||
default:
|
default:
|
||||||
return String.class;
|
return String.class;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdrtrunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
public enum BroadcastServerType
|
public enum BroadcastServerType
|
||||||
@@ -23,17 +23,17 @@ public enum BroadcastServerType
|
|||||||
/**
|
/**
|
||||||
* Broadcastify feeds (ie streaming) service
|
* Broadcastify feeds (ie streaming) service
|
||||||
*/
|
*/
|
||||||
BROADCASTIFY("Broadcastify Feed", "/images/broadcastify.png"), //Icecast Server 2.3.2
|
BROADCASTIFY("Broadcastify Feed", "images/broadcastify.png"), //Icecast Server 2.3.2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcastify calls - completed audio recording push service
|
* Broadcastify calls - completed audio recording push service
|
||||||
*/
|
*/
|
||||||
BROADCASTIFY_CALL("Broadcastify Call", "/images/broadcastify.png"),
|
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
||||||
|
|
||||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
|
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||||
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
|
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||||
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
|
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||||
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
|
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||||
UNKNOWN("Unknown", null);
|
UNKNOWN("Unknown", null);
|
||||||
|
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdrtrunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
public enum BroadcastState
|
public enum BroadcastState
|
||||||
@@ -103,10 +103,10 @@ public enum BroadcastState
|
|||||||
private String mLabel;
|
private String mLabel;
|
||||||
private boolean mErrorState;
|
private boolean mErrorState;
|
||||||
|
|
||||||
private BroadcastState(String label, boolean connected)
|
BroadcastState(String label, boolean errorState)
|
||||||
{
|
{
|
||||||
mLabel = label;
|
mLabel = label;
|
||||||
mErrorState = connected;
|
mErrorState = errorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,23 +14,29 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
|
import io.github.dsheirer.icon.Icon;
|
||||||
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table of broadcast streams and statuses.
|
||||||
|
*/
|
||||||
public class BroadcastStatusPanel extends JPanel
|
public class BroadcastStatusPanel extends JPanel
|
||||||
{
|
{
|
||||||
private JTable mTable;
|
private JTable mTable;
|
||||||
@@ -41,6 +46,12 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private String mPreferenceKey;
|
private String mPreferenceKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param broadcastModel to access the streams
|
||||||
|
* @param userPreferences for configuring the panel
|
||||||
|
* @param preferenceKey to store column preferences for this panel.
|
||||||
|
*/
|
||||||
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
|
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
|
||||||
{
|
{
|
||||||
mBroadcastModel = broadcastModel;
|
mBroadcastModel = broadcastModel;
|
||||||
@@ -65,6 +76,7 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
|
||||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
||||||
|
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
|
||||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
||||||
|
|
||||||
mScrollPane = new JScrollPane(mTable);
|
mScrollPane = new JScrollPane(mTable);
|
||||||
@@ -72,6 +84,37 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
add(mScrollPane);
|
add(mScrollPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ServerTypeRenderer extends DefaultTableCellRenderer
|
||||||
|
{
|
||||||
|
public ServerTypeRenderer()
|
||||||
|
{
|
||||||
|
setOpaque(true);
|
||||||
|
setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||||
|
{
|
||||||
|
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
|
|
||||||
|
if(value instanceof BroadcastServerType broadcastServerType)
|
||||||
|
{
|
||||||
|
component.setText(broadcastServerType.toString());
|
||||||
|
Icon icon = new Icon("empty", broadcastServerType.getIconPath());
|
||||||
|
ImageIcon imageIcon = icon.getIcon();
|
||||||
|
ImageIcon scaledIcon = IconModel.getScaledIcon(imageIcon, 13);
|
||||||
|
component.setIcon(scaledIcon);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
component.setText(null);
|
||||||
|
component.setIcon(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom cell renderer for the broadcast state column.
|
* Custom cell renderer for the broadcast state column.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,13 +20,20 @@ package io.github.dsheirer.audio.broadcast.icecast;
|
|||||||
|
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
|
||||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import org.apache.mina.core.RuntimeIoException;
|
import org.apache.mina.core.RuntimeIoException;
|
||||||
import org.apache.mina.core.buffer.IoBuffer;
|
import org.apache.mina.core.buffer.IoBuffer;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
@@ -42,13 +49,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
||||||
@@ -143,8 +143,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
mSocketConnector = new NioSocketConnector();
|
mSocketConnector = new NioSocketConnector();
|
||||||
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
||||||
|
|
||||||
// mSocketConnector.getFilterChain().addLast("logger",
|
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
|
||||||
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
|
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
|
||||||
|
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
|
||||||
|
|
||||||
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
||||||
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
|
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
|
||||||
@@ -315,10 +316,49 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
|
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
|
||||||
* OK response and then play like everything is well and good.
|
* OK response and then play like everything is well and good.
|
||||||
*/
|
*/
|
||||||
if(cause instanceof ArrayIndexOutOfBoundsException &&
|
if(cause instanceof ArrayIndexOutOfBoundsException)
|
||||||
((ProtocolDecoderException)throwable).getHexdump().startsWith(HTTP_1_0_OK_HEX_DUMP))
|
|
||||||
{
|
{
|
||||||
setBroadcastState(BroadcastState.CONNECTED);
|
String hexDump = ((ProtocolDecoderException)throwable).getHexdump();
|
||||||
|
|
||||||
|
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HttpDumpMessage message = new HttpDumpMessage(hexDump);
|
||||||
|
|
||||||
|
if(message.hasHttpResponseCode())
|
||||||
|
{
|
||||||
|
switch(message.getHttpResponseCode())
|
||||||
|
{
|
||||||
|
case 403: //Forbidden
|
||||||
|
if(message.toString().contains("Mountpoint in use"))
|
||||||
|
{
|
||||||
|
mLog.error("Stream [" + getStreamName() + "] - unable to connect - mountpoint in use");
|
||||||
|
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -374,4 +414,97 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for parsing HTTP response messages from Icecast 2.4.2+
|
||||||
|
*/
|
||||||
|
public class HttpDumpMessage
|
||||||
|
{
|
||||||
|
private String mHexDump;
|
||||||
|
private String mMessage;
|
||||||
|
private int mHttpResponseCode = -1;
|
||||||
|
|
||||||
|
public HttpDumpMessage(String hexDump)
|
||||||
|
{
|
||||||
|
mHexDump = hexDump;
|
||||||
|
|
||||||
|
String[] split = mHexDump.split(" ");
|
||||||
|
byte[] bytes = new byte[split.length];
|
||||||
|
|
||||||
|
int pointer = 0;
|
||||||
|
for(String a : split)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int value = Integer.parseInt(a, 16);
|
||||||
|
bytes[pointer++] = (byte) (value & 0xFF);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
pointer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mMessage = new String(bytes);
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
|
||||||
|
Matcher m = pattern.matcher(mMessage);
|
||||||
|
|
||||||
|
if(m.find())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mHttpResponseCode = Integer.parseInt(m.group(1));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Unable to parse HTTP response code that was matched from message: " + m.group(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if an HTTP response code was parsed from the message
|
||||||
|
* @return true if it was parsed.
|
||||||
|
*/
|
||||||
|
public boolean hasHttpResponseCode()
|
||||||
|
{
|
||||||
|
return mHttpResponseCode != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response code parsed from the hexdump message
|
||||||
|
* @return value.
|
||||||
|
*/
|
||||||
|
public int getHttpResponseCode()
|
||||||
|
{
|
||||||
|
return mHttpResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
String text = "HTTP/1.0 403 Forbidden";
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
|
||||||
|
|
||||||
|
Matcher m = pattern.matcher(text);
|
||||||
|
|
||||||
|
if(m.find())
|
||||||
|
{
|
||||||
|
mLog.info("Matching Group Count: " + m.groupCount());
|
||||||
|
mLog.info("Match 0: " + m.group(0));
|
||||||
|
mLog.info("Match 1: " + m.group(1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("No Match");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,19 @@ package io.github.dsheirer.audio.broadcast.icecast;
|
|||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
|
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
|
||||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.channels.UnresolvedAddressException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.apache.mina.core.RuntimeIoException;
|
import org.apache.mina.core.RuntimeIoException;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||||
@@ -37,14 +43,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.nio.channels.UnresolvedAddressException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
||||||
@@ -215,7 +213,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
//Only set broadcast state to disconnected if it's not already in an error state, to prevent a restart. We
|
||||||
|
//want to preserve the error state that got us here, so the user can see it.
|
||||||
|
if(!getBroadcastState().isErrorState())
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
mLastConnectionAttempt = System.currentTimeMillis();
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package io.github.dsheirer.audio.convert;
|
package io.github.dsheirer.audio.convert;
|
||||||
|
|
||||||
import io.github.dsheirer.audio.AudioFormats;
|
import io.github.dsheirer.audio.AudioFormats;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
|
||||||
@@ -29,8 +30,8 @@ import javax.sound.sampled.AudioFormat;
|
|||||||
public enum InputAudioFormat
|
public enum InputAudioFormat
|
||||||
{
|
{
|
||||||
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
|
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
|
||||||
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz"),
|
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz (default)"),
|
||||||
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz (default)"),
|
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz"),
|
||||||
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
|
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
|
||||||
|
|
||||||
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
|
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
|
||||||
@@ -41,6 +42,17 @@ public enum InputAudioFormat
|
|||||||
private AudioFormat mAudioFormat;
|
private AudioFormat mAudioFormat;
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set that includes sample rates of 8 or 16 kHz
|
||||||
|
*/
|
||||||
|
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16 = EnumSet.of(SR_8000, SR_32_8000, SR_16000, SR_32_16000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set that includes sample rates of 8, 16, or 22 kHz
|
||||||
|
*/
|
||||||
|
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16_22 = EnumSet.of(SR_8000, SR_32_8000, SR_16000,
|
||||||
|
SR_32_16000, SR_22050, SR_32_22050);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
* @param audioFormat for the specified sample rate
|
* @param audioFormat for the specified sample rate
|
||||||
@@ -56,7 +68,7 @@ public enum InputAudioFormat
|
|||||||
*/
|
*/
|
||||||
public static InputAudioFormat getDefault()
|
public static InputAudioFormat getDefault()
|
||||||
{
|
{
|
||||||
return SR_22050;
|
return SR_16000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,19 +57,20 @@ public class MP3AudioConverter implements IAudioConverter
|
|||||||
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio)
|
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio)
|
||||||
{
|
{
|
||||||
mInputAudioFormat = inputAudioFormat;
|
mInputAudioFormat = inputAudioFormat;
|
||||||
|
|
||||||
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
||||||
if(inputAudioFormat.getSampleRate() != mEncoder.getEffectiveSampleRate())
|
|
||||||
{
|
|
||||||
mLog.warn("MP3 encoder setting '" + setting + "' doesn't support sample rate '" + inputAudioFormat + "', resampling disabled. Increase bitrate or decrease sample rate to fix.");
|
|
||||||
mInputAudioFormat = InputAudioFormat.SR_8000;
|
|
||||||
mEncoder = LameFactory.getLameEncoder(mInputAudioFormat, setting);
|
|
||||||
}
|
|
||||||
|
|
||||||
mNormalizeAudio = normalizeAudio;
|
mNormalizeAudio = normalizeAudio;
|
||||||
|
|
||||||
|
//Ensure input audio sample rate is supported for the MP3 setting and update as necessary - should never happen.
|
||||||
|
if((mInputAudioFormat.getSampleRate() - mEncoder.getEffectiveSampleRate()) > 1.0)
|
||||||
|
{
|
||||||
|
mInputAudioFormat = InputAudioFormat.getDefault();
|
||||||
|
mLog.warn("MP3 setting [" + setting + "] does not support requested input audio sample rate [" +
|
||||||
|
inputAudioFormat + "] - using default sample rate [" + mInputAudioFormat + "]");
|
||||||
|
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
||||||
|
}
|
||||||
|
|
||||||
//Resampling is only required if desired input sample rate is not system default of 8kHz
|
//Resampling is only required if desired input sample rate is not system default of 8kHz
|
||||||
if(inputAudioFormat != InputAudioFormat.SR_8000 && inputAudioFormat != InputAudioFormat.SR_32_8000)
|
if(mInputAudioFormat != InputAudioFormat.SR_8000 && mInputAudioFormat != InputAudioFormat.SR_32_8000)
|
||||||
{
|
{
|
||||||
mResampler = LameFactory.getResampler(inputAudioFormat);
|
mResampler = LameFactory.getResampler(inputAudioFormat);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public class MP3Header
|
|||||||
*/
|
*/
|
||||||
public static boolean isPadded(byte[] frame, int offset)
|
public static boolean isPadded(byte[] frame, int offset)
|
||||||
{
|
{
|
||||||
return (frame[offset + 2] & 0x01) == 1;
|
return (frame[offset + 2] & 0x02) == 0x02;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.audio.convert;
|
package io.github.dsheirer.audio.convert;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import net.sourceforge.lame.mp3.Lame;
|
import net.sourceforge.lame.mp3.Lame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,6 +81,30 @@ public enum MP3Setting
|
|||||||
return mSetting;
|
return mSetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input audio formats that are supported by the MP3Setting value.
|
||||||
|
* @return list of supported input audio sample rates.
|
||||||
|
*/
|
||||||
|
public List<InputAudioFormat> getSupportedSampleRates()
|
||||||
|
{
|
||||||
|
switch(this)
|
||||||
|
{
|
||||||
|
case CBR_16:
|
||||||
|
{
|
||||||
|
return InputAudioFormat.SAMPLE_RATES_8_16.stream().toList();
|
||||||
|
}
|
||||||
|
case CBR_32:
|
||||||
|
case VBR_7:
|
||||||
|
{
|
||||||
|
return InputAudioFormat.SAMPLE_RATES_8_16_22.stream().toList();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return Arrays.stream(InputAudioFormat.values()).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,35 +1,38 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is 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.bits;
|
package io.github.dsheirer.bits;
|
||||||
|
|
||||||
import io.github.dsheirer.edac.CRC;
|
import io.github.dsheirer.edac.CRC;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.BitSet;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
import java.util.BitSet;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class BinaryMessage extends BitSet
|
public class BinaryMessage extends BitSet
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(BinaryMessage.class);
|
||||||
|
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||||
|
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final String UTF_8 = "UTF-8";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical (ie constructed) size of this bitset, despite the actual size of
|
* Logical (ie constructed) size of this bitset, despite the actual size of
|
||||||
@@ -1038,6 +1041,51 @@ public class BinaryMessage extends BitSet
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the hexadecimal text into a binary message
|
||||||
|
* @param hex text to load from
|
||||||
|
* @return loaded binary message
|
||||||
|
*/
|
||||||
|
public static BinaryMessage loadHex(String hex)
|
||||||
|
{
|
||||||
|
if(!hex.matches("[0-9A-F]*"))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Message must contain only 0-9 and A-F hexadecimal characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = hex.length() / 2;
|
||||||
|
|
||||||
|
if(length * 2 < hex.length())
|
||||||
|
{
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryMessage buffer = new BinaryMessage(length * 8);
|
||||||
|
|
||||||
|
int bufferOffset = 0;
|
||||||
|
for(int x = 0; x < hex.length(); x += 2)
|
||||||
|
{
|
||||||
|
int endIndex = x + 2;
|
||||||
|
if(endIndex > hex.length())
|
||||||
|
{
|
||||||
|
endIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
String rawHex = hex.substring(x, endIndex);
|
||||||
|
|
||||||
|
if(rawHex.length() == 1)
|
||||||
|
{
|
||||||
|
rawHex = rawHex + "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
int value = Integer.parseInt(rawHex, 16);
|
||||||
|
buffer.setByte(bufferOffset, (byte)(value & 0xFF));
|
||||||
|
bufferOffset += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Left rotates the bits between start and end indices, number of places.
|
* Left rotates the bits between start and end indices, number of places.
|
||||||
*/
|
*/
|
||||||
@@ -1133,19 +1181,101 @@ public class BinaryMessage extends BitSet
|
|||||||
this.xor(mask);
|
this.xor(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
/**
|
||||||
|
* Parse ISO-7 bit characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
*/
|
||||||
|
public String parseISO7(int offset, int characterCount)
|
||||||
{
|
{
|
||||||
BinaryMessage message = new BinaryMessage(10);
|
byte[] bytes = new byte[characterCount];
|
||||||
message.set(1);
|
|
||||||
message.set(4);
|
|
||||||
message.set(5);
|
|
||||||
message.set(8);
|
|
||||||
message.set(9);
|
|
||||||
|
|
||||||
System.out.println("Hex: " + message.toHexString() + " Size:" + message.size());
|
for(int x = 0; x < characterCount; x++)
|
||||||
for(int x = 0; x < 10; x++)
|
|
||||||
{
|
{
|
||||||
System.out.println(x + ": " + message.getHex(x, x + 10));
|
bytes[x] = getByte(CHARACTER_7_BIT, x * 7 + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse ISO-8 bit characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
*/
|
||||||
|
public String parseISO8(int offset, int characterCount)
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[characterCount];
|
||||||
|
|
||||||
|
for(int x = 0; x < characterCount; x++)
|
||||||
|
{
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse UTF-8 characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
* @throws UnsupportedEncodingException if UTF-8 encoding is not supported.
|
||||||
|
*/
|
||||||
|
public String parseUTF8(int offset, int characterCount) throws UnsupportedEncodingException
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[characterCount];
|
||||||
|
|
||||||
|
for(int x = 0; x < characterCount; x++)
|
||||||
|
{
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes, UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse 16-bit unicode characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
*/
|
||||||
|
public String parseUnicode(int offset, int characterCount)
|
||||||
|
{
|
||||||
|
int length = characterCount * 2;
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
|
||||||
|
for(int x = 0; x < length; x++)
|
||||||
|
{
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse 4-bit Binary Coded Decimal (BCD) characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
*/
|
||||||
|
public String parseBCD4(int offset, int characterCount)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
while(count < characterCount)
|
||||||
|
{
|
||||||
|
int nibble = getNibble(offset);
|
||||||
|
//This will parse digits 0-9 and anything higher will parse as hexadecimal A-F
|
||||||
|
sb.append(Integer.toHexString(nibble).toUpperCase());
|
||||||
|
offset += 4;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.channel.metadata;
|
package io.github.dsheirer.channel.metadata;
|
||||||
|
|
||||||
@@ -28,10 +27,11 @@ import io.github.dsheirer.module.decode.event.DecodeEventPanel;
|
|||||||
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
|
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
|
||||||
import io.github.dsheirer.playlist.PlaylistManager;
|
import io.github.dsheirer.playlist.PlaylistManager;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
|
import java.awt.Color;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import java.awt.Color;
|
|
||||||
|
|
||||||
public class NowPlayingPanel extends JPanel
|
public class NowPlayingPanel extends JPanel
|
||||||
{
|
{
|
||||||
@@ -45,13 +45,14 @@ public class NowPlayingPanel extends JPanel
|
|||||||
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
|
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
|
||||||
* messages, events, and spectral view.
|
* messages, events, and spectral view.
|
||||||
*/
|
*/
|
||||||
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences)
|
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences,
|
||||||
|
SettingsManager settingsManager)
|
||||||
{
|
{
|
||||||
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
|
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
|
||||||
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
|
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
|
||||||
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
|
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
|
||||||
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
|
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
|
||||||
mChannelPowerPanel = new ChannelPowerPanel(playlistManager);
|
mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@@ -68,6 +69,10 @@ public class NowPlayingPanel extends JPanel
|
|||||||
tabbedPane.setFont(this.getFont());
|
tabbedPane.setFont(this.getFont());
|
||||||
tabbedPane.setForeground(Color.BLACK);
|
tabbedPane.setForeground(Color.BLACK);
|
||||||
|
|
||||||
|
//Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing
|
||||||
|
tabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(tabbedPane.getSelectedIndex() == tabbedPane
|
||||||
|
.indexOfComponent(mChannelPowerPanel)));
|
||||||
|
|
||||||
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
|
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
|
||||||
splitPane.setShowGripper(true);
|
splitPane.setShowGripper(true);
|
||||||
splitPane.add(mChannelMetadataPanel);
|
splitPane.add(mChannelMetadataPanel);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,6 +32,8 @@ import io.github.dsheirer.preference.UserPreferences;
|
|||||||
import io.github.dsheirer.settings.SettingsManager;
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import io.github.dsheirer.source.tuner.ui.TunerViewPanel;
|
import io.github.dsheirer.source.tuner.ui.TunerViewPanel;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
import jiconfont.icons.font_awesome.FontAwesome;
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
import jiconfont.swing.IconFontSwing;
|
import jiconfont.swing.IconFontSwing;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
@@ -41,8 +43,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
|
|
||||||
public class ControllerPanel extends JPanel
|
public class ControllerPanel extends JPanel
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,7 @@ public class ControllerPanel extends JPanel
|
|||||||
{
|
{
|
||||||
mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager,
|
mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager,
|
||||||
playlistManager.getAliasModel());
|
playlistManager.getAliasModel());
|
||||||
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences);
|
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager);
|
||||||
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager);
|
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager);
|
||||||
mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences);
|
mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.controller.channel;
|
package io.github.dsheirer.controller.channel;
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ public class ChannelSelectionManager implements Listener<ChannelEvent>
|
|||||||
public ChannelSelectionManager(ChannelModel channelModel)
|
public ChannelSelectionManager(ChannelModel channelModel)
|
||||||
{
|
{
|
||||||
mChannelEventListener = channelModel;
|
mChannelEventListener = channelModel;
|
||||||
channelModel.addListener(this::receive);
|
channelModel.addListener(ChannelSelectionManager.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* *********************************************************************************************************************
|
* *****************************************************************************
|
||||||
* sdr-trunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* 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.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *********************************************************************************************************************
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.dsp.filter.channelizer;
|
package io.github.dsheirer.dsp.filter.channelizer;
|
||||||
|
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ChannelCalculator
|
public class ChannelCalculator
|
||||||
{
|
{
|
||||||
|
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -546,13 +549,13 @@ public class ChannelCalculator
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("Channel Calculator Settings");
|
sb.append("Channel Calculator | Tuner SR:").append(FREQUENCY_FORMAT.format(getSampleRate() / 1E6D));
|
||||||
sb.append(" Channels:").append(getChannelCount());
|
sb.append(" CF:").append(FREQUENCY_FORMAT.format(getCenterFrequency() / 1E6D));
|
||||||
sb.append(" Sample Rate:").append(getSampleRate());
|
sb.append(" MIN:").append(FREQUENCY_FORMAT.format(getMinimumFrequency() / 1E6D));
|
||||||
sb.append(" Channel Bandwidth:").append(getChannelBandwidth());
|
sb.append(" MAX:").append(FREQUENCY_FORMAT.format(getMaximumFrequency() / 1E6D));
|
||||||
sb.append(" Channel Rate:").append(getChannelSampleRate());
|
sb.append(" | Channel COUNT:").append(getChannelCount());
|
||||||
sb.append(" Min:").append(getMinimumFrequency());
|
sb.append(" BW:").append(FREQUENCY_FORMAT.format(getChannelBandwidth() / 1E6D));
|
||||||
sb.append(" Max:").append(getMaximumFrequency());
|
sb.append(" SR:").append(FREQUENCY_FORMAT.format(getChannelSampleRate() / 1E6D));
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -35,6 +35,7 @@ import io.github.dsheirer.source.tuner.TunerController;
|
|||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
import io.github.dsheirer.util.Dispatcher;
|
import io.github.dsheirer.util.Dispatcher;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -43,6 +44,7 @@ import java.util.Map;
|
|||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -65,6 +67,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class PolyphaseChannelManager implements ISourceEventProcessor
|
public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||||
{
|
{
|
||||||
|
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||||
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
||||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||||
@@ -123,6 +126,34 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate());
|
this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a description of the state of this manager.
|
||||||
|
*/
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Polyphase Channel Manager Providing [").append(mChannelSources.size()).append("] Channels");
|
||||||
|
sb.append("\n\t").append(mChannelCalculator);
|
||||||
|
for(PolyphaseChannelSource pcs: mChannelSources)
|
||||||
|
{
|
||||||
|
List<Integer> indexes = pcs.getOutputProcessorIndexes();
|
||||||
|
double sampleRate = pcs.getSampleRate();
|
||||||
|
long indexCenterFrequency = pcs.getIndexCenterFrequency();
|
||||||
|
long appliedFrequencyOffset = pcs.getFrequencyOffset();
|
||||||
|
long requestedCenterFrequency = pcs.getFrequency();
|
||||||
|
|
||||||
|
sb.append("\n\tPolyphase | Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
|
||||||
|
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
|
||||||
|
sb.append(" BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
|
||||||
|
sb.append(" | Channel CF: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
|
||||||
|
sb.append(" REQUESTED CF: ").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
|
||||||
|
sb.append(" MIXER:").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
|
||||||
|
sb.append(" | Polyphase Indices: ").append(indexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public void stopAllChannels()
|
public void stopAllChannels()
|
||||||
{
|
{
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
@@ -278,7 +309,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
//no-op
|
//no-op
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mLog.info("Unrecognized source event: " + sourceEvent.toString());
|
mLog.info("Unrecognized source event: " + sourceEvent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,9 +458,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
source.dispose();
|
source.dispose();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
|
||||||
//ignore
|
|
||||||
break;
|
|
||||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||||
//Rebroadcast so that the tuner source can process this event
|
//Rebroadcast so that the tuner source can process this event
|
||||||
mSourceEventBroadcaster.broadcast(sourceEvent);
|
mSourceEventBroadcaster.broadcast(sourceEvent);
|
||||||
@@ -445,16 +473,18 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
/**
|
/**
|
||||||
* Processes the incoming buffer stream from the provider and transfers the buffers to the polyphase channelizer.
|
* Processes the incoming buffer stream from the provider and transfers the buffers to the polyphase channelizer.
|
||||||
*
|
*
|
||||||
* This monitor incorporates a queue for receiving source events that affect configuration of the channelizer so
|
* This monitor incorporates a source event handler that queues a center frequency update so that it can be
|
||||||
* that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer
|
* handled on the buffer processing thread, avoiding having to lock on the output processor thread. Since we
|
||||||
* and/or the channel sources and output processors.
|
* anticipate that these two threads will contend for access to this update required flag, we use an update lock
|
||||||
|
* to protect access to the flag.
|
||||||
*/
|
*/
|
||||||
public class NativeBufferReceiver implements Listener<INativeBuffer>
|
public class NativeBufferReceiver implements Listener<INativeBuffer>
|
||||||
{
|
{
|
||||||
private boolean mOutputProcessorUpdateRequired = false;
|
private boolean mOutputProcessorUpdateRequired = false;
|
||||||
|
private ReentrantLock mUpdateFlagLock = new ReentrantLock();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues the source event for deferred execution on the buffer processing thread.
|
* Processes tuner center frequency change source events to flag when output processors need updating.
|
||||||
* @param event that affects configuration of the channelizer (frequency or sample rate change events)
|
* @param event that affects configuration of the channelizer (frequency or sample rate change events)
|
||||||
*/
|
*/
|
||||||
public void receive(SourceEvent event)
|
public void receive(SourceEvent event)
|
||||||
@@ -463,14 +493,31 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
|
|
||||||
if(mChannelCalculator.getCenterFrequency() != frequency)
|
if(mChannelCalculator.getCenterFrequency() != frequency)
|
||||||
{
|
{
|
||||||
mChannelCalculator.setCenterFrequency(frequency);
|
mUpdateFlagLock.lock();
|
||||||
mOutputProcessorUpdateRequired = true;
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Update the channel calculator frequency so that it's ready when the output processor update occurs
|
||||||
|
mChannelCalculator.setCenterFrequency(frequency);
|
||||||
|
mOutputProcessorUpdateRequired = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mUpdateFlagLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process native buffer streams and update polyphase output channels when the parent tuner center
|
||||||
|
* frequency changes.
|
||||||
|
* @param nativeBuffer of sample to process.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void receive(INativeBuffer nativeBuffer)
|
public void receive(INativeBuffer nativeBuffer)
|
||||||
{
|
{
|
||||||
|
mUpdateFlagLock.lock();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if(mOutputProcessorUpdateRequired)
|
if(mOutputProcessorUpdateRequired)
|
||||||
@@ -478,21 +525,28 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
updateOutputProcessors();
|
updateOutputProcessors();
|
||||||
mOutputProcessorUpdateRequired = false;
|
mOutputProcessorUpdateRequired = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mUpdateFlagLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
if(mPolyphaseChannelizer != null)
|
if(mPolyphaseChannelizer != null)
|
||||||
|
{
|
||||||
|
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
|
||||||
|
|
||||||
|
while(iterator.hasNext())
|
||||||
{
|
{
|
||||||
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
|
try
|
||||||
|
|
||||||
while(iterator.hasNext())
|
|
||||||
{
|
{
|
||||||
mPolyphaseChannelizer.receive(iterator.next());
|
mPolyphaseChannelizer.receive(iterator.next());
|
||||||
}
|
}
|
||||||
|
catch(Throwable throwable)
|
||||||
|
{
|
||||||
|
mLog.error("Error", throwable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Throwable throwable)
|
|
||||||
{
|
|
||||||
mLog.error("Error", throwable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,6 +28,7 @@ import io.github.dsheirer.source.SourceEvent;
|
|||||||
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
|
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -45,9 +46,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
||||||
private double mChannelSampleRate;
|
private double mChannelSampleRate;
|
||||||
private long mIndexCenterFrequency;
|
private long mIndexCenterFrequency;
|
||||||
private long mChannelFrequencyCorrection;
|
|
||||||
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
|
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
|
||||||
|
|
||||||
|
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
|
||||||
|
private double mTunerSampleRate;
|
||||||
|
private double mTunerCenterFrequency;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
*
|
*
|
||||||
@@ -66,6 +71,41 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
updateOutputProcessor(channelCalculator, filterManager);
|
updateOutputProcessor(channelCalculator, filterManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the state or configuration of the output processor.
|
||||||
|
*/
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
return mPolyphaseChannelOutputProcessor.getStateDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current output processor indexes.
|
||||||
|
* @return indexes
|
||||||
|
*/
|
||||||
|
public List<Integer> getOutputProcessorIndexes()
|
||||||
|
{
|
||||||
|
return mOutputProcessorIndexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample rate or bandwidth of the tuner providing input to the channelizer.
|
||||||
|
* @return sample rate
|
||||||
|
*/
|
||||||
|
public double getTunerSampleRate()
|
||||||
|
{
|
||||||
|
return mTunerSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Center tuned frequency of the tuner providing input to the channelizer.
|
||||||
|
* @return center frequency
|
||||||
|
*/
|
||||||
|
public double getTunerCenterFrequency()
|
||||||
|
{
|
||||||
|
return mTunerCenterFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
@@ -125,6 +165,11 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
//calculator will throw an IllegalArgException ... handled below
|
//calculator will throw an IllegalArgException ... handled below
|
||||||
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
|
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
|
||||||
|
|
||||||
|
mOutputProcessorIndexes.clear();
|
||||||
|
mOutputProcessorIndexes.addAll(indexes);
|
||||||
|
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
|
||||||
|
mTunerSampleRate = channelCalculator.getSampleRate();
|
||||||
|
|
||||||
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
|
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
|
||||||
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
|
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
|
||||||
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
|
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
|
||||||
@@ -137,7 +182,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
float[] filter = filterManager.getFilter(channelCalculator.getSampleRate(),
|
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
|
||||||
channelCalculator.getChannelBandwidth(), indexes.size());
|
channelCalculator.getChannelBandwidth(), indexes.size());
|
||||||
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
|
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
|
||||||
}
|
}
|
||||||
@@ -263,59 +308,32 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getChannelFrequencyCorrection()
|
|
||||||
{
|
|
||||||
return mChannelFrequencyCorrection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts the frequency correction value that is being applied to the channelized output stream by the
|
* Sets the center frequency for this channel.
|
||||||
* polyphase channel output processor.
|
|
||||||
*
|
|
||||||
* @param value to apply for frequency correction in hertz
|
|
||||||
*/
|
|
||||||
protected void setChannelFrequencyCorrection(long value)
|
|
||||||
{
|
|
||||||
mChannelFrequencyCorrection = value;
|
|
||||||
updateFrequencyOffset();
|
|
||||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the center frequency for this channel.frequency for the incoming sample stream channel results and resets frequency correction to zero.
|
|
||||||
* @param frequency in hertz
|
* @param frequency in hertz
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setFrequency(long frequency)
|
public void setFrequency(long frequency)
|
||||||
{
|
{
|
||||||
mIndexCenterFrequency = frequency;
|
mIndexCenterFrequency = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
//Set frequency correction to zero to trigger an update to the mixer and allow downstream monitors to
|
/**
|
||||||
//recalculate the frequency error correction again
|
* Center frequency from the incoming index channel(s).
|
||||||
setChannelFrequencyCorrection(0);
|
* @return frequency in hertz
|
||||||
|
*/
|
||||||
|
public long getIndexCenterFrequency()
|
||||||
|
{
|
||||||
|
return mIndexCenterFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
|
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
|
||||||
* within the channel
|
* within the channel
|
||||||
*/
|
*/
|
||||||
private long getFrequencyOffset()
|
public long getFrequencyOffset()
|
||||||
{
|
{
|
||||||
return mIndexCenterFrequency - getTunerChannel().getFrequency() + mChannelFrequencyCorrection;
|
return mIndexCenterFrequency - getTunerChannel().getFrequency();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the frequency offset being applied by the output processor. Calculates the offset using the center
|
|
||||||
* frequency of the incoming polyphase channel results and the desired output channel frequency adjusted by any
|
|
||||||
* requested frequency correction value.
|
|
||||||
*/
|
|
||||||
private void updateFrequencyOffset()
|
|
||||||
{
|
|
||||||
if(mPolyphaseChannelOutputProcessor != null)
|
|
||||||
{
|
|
||||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
package io.github.dsheirer.dsp.filter.channelizer;
|
package io.github.dsheirer.dsp.filter.channelizer;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
|
import java.util.Arrays;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
import org.jtransforms.fft.FloatFFT_1D;
|
import org.jtransforms.fft.FloatFFT_1D;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -65,6 +66,20 @@ public class TwoChannelSynthesizerM2
|
|||||||
init(filter);
|
init(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the state/configuration of this synthesizer to support debugging.
|
||||||
|
* @return state description.
|
||||||
|
*/
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Two-Channel Synthesizer");
|
||||||
|
sb.append("\n\t\t Interleaved Filter: " + Arrays.toString(mIQInterleavedFilter));
|
||||||
|
sb.append("\n\t\t Serpentine Buffer Length: ").append(mSerpentineDataBuffer.length);
|
||||||
|
sb.append("\n\t\t Filter Vector Product Length: ").append(mFilterVectorProduct.length);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the synthesizer filter and data buffers for operation.
|
* Initializes the synthesizer filter and data buffers for operation.
|
||||||
*
|
*
|
||||||
@@ -73,7 +88,6 @@ public class TwoChannelSynthesizerM2
|
|||||||
private void init(float[] filter)
|
private void init(float[] filter)
|
||||||
{
|
{
|
||||||
int tapsPerChannel = (int) FastMath.ceil(filter.length / 2);
|
int tapsPerChannel = (int) FastMath.ceil(filter.length / 2);
|
||||||
|
|
||||||
mIQInterleavedFilter = getInterleavedFilter(filter, tapsPerChannel);
|
mIQInterleavedFilter = getInterleavedFilter(filter, tapsPerChannel);
|
||||||
mSerpentineDataBuffer = new float[mIQInterleavedFilter.length];
|
mSerpentineDataBuffer = new float[mIQInterleavedFilter.length];
|
||||||
mFilterVectorProduct = new float[mIQInterleavedFilter.length];
|
mFilterVectorProduct = new float[mIQInterleavedFilter.length];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,11 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface IPolyphaseChannelOutputProcessor
|
public interface IPolyphaseChannelOutputProcessor
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Description of the state/configuration of this output processor
|
||||||
|
*/
|
||||||
|
String getStateDescription();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start processing channel results
|
* Start processing channel results
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -45,6 +45,16 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("One Channel Output Processor");
|
||||||
|
sb.append("\n\tIndex (doubled) [").append(mChannelOffset).append("]");
|
||||||
|
sb.append("\n\tMixer Assembler: none");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSynthesisFilter(float[] filter)
|
public void setSynthesisFilter(float[] filter)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -42,6 +42,19 @@ public class TwoChannelMixerAssembler extends MixerAssembler
|
|||||||
super(gain);
|
super(gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the state/configuration of this mixer assembler.
|
||||||
|
*/
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Two Channel Mixer Assembler - State Description:");
|
||||||
|
sb.append("\n\tTwo-Channel Synthesizer: ").append(mTwoChannelSynthesizer.getStateDescription());
|
||||||
|
sb.append("\n\tMixer Frequency: ").append(getMixer().getFrequency());
|
||||||
|
sb.append("\n\tGain: ").append(getGain().getGain());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the filter coefficients to use for two-channel synthesis
|
* Sets the filter coefficients to use for two-channel synthesis
|
||||||
* @param filter
|
* @param filter
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -49,6 +49,16 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
setSynthesisFilter(filter);
|
setSynthesisFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Two Channel Output Processor");
|
||||||
|
sb.append("\n\tIndices (doubled) 1 [").append(mChannelOffset1).append("] 2 [").append(mChannelOffset2).append("]");
|
||||||
|
sb.append("\n\tMixer Assembler: ").append(mMixerAssembler.getStateDescription());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
|
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -41,7 +41,7 @@ public abstract class ComplexGain
|
|||||||
* Gain value that will be applied to complex samples
|
* Gain value that will be applied to complex samples
|
||||||
* @return gain value
|
* @return gain value
|
||||||
*/
|
*/
|
||||||
protected float getGain()
|
public float getGain()
|
||||||
{
|
{
|
||||||
return mGain;
|
return mGain;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -72,6 +72,15 @@ public abstract class ComplexMixer
|
|||||||
return mOscillator.hasFrequency();
|
return mOscillator.hasFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current frequency of this oscillator
|
||||||
|
* @return frequency in hertz
|
||||||
|
*/
|
||||||
|
public double getFrequency()
|
||||||
|
{
|
||||||
|
return mOscillator.getFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the sample rate of the underlying oscillator
|
* Sets the sample rate of the underlying oscillator
|
||||||
* @param sampleRate in Hertz
|
* @param sampleRate in Hertz
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.dsp.oscillator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additive white gaussian noise wrapper around a complex oscillator that mixes in gaussian noise to the complex
|
||||||
|
* samples generated by the underlying complex oscillator.
|
||||||
|
*/
|
||||||
|
public class AWGNOscillator implements IComplexOscillator
|
||||||
|
{
|
||||||
|
private final Random mRandom = new Random();
|
||||||
|
private IComplexOscillator mComplexOscillator;
|
||||||
|
private float mNoiseScale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param complexOscillator to generate samples
|
||||||
|
* @param noiseScale to apply to generated samples to reduce the power of the noise and increase the SNR.
|
||||||
|
*/
|
||||||
|
public AWGNOscillator(IComplexOscillator complexOscillator, float noiseScale)
|
||||||
|
{
|
||||||
|
mComplexOscillator = complexOscillator;
|
||||||
|
mNoiseScale = noiseScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce the next scaled noise sample.
|
||||||
|
* @return next scaled noise sample.
|
||||||
|
*/
|
||||||
|
private float nextNoise()
|
||||||
|
{
|
||||||
|
return (float)mRandom.nextGaussian() * mNoiseScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] generate(int sampleCount)
|
||||||
|
{
|
||||||
|
float[] generated = mComplexOscillator.generate(sampleCount);
|
||||||
|
|
||||||
|
for(int x = 0; x < generated.length; x++)
|
||||||
|
{
|
||||||
|
generated[x] += nextNoise();
|
||||||
|
}
|
||||||
|
|
||||||
|
return generated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComplexSamples generateComplexSamples(int sampleCount, long timestamp)
|
||||||
|
{
|
||||||
|
ComplexSamples samples = mComplexOscillator.generateComplexSamples(sampleCount, timestamp);
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.i().length; x++)
|
||||||
|
{
|
||||||
|
samples.i()[x] += nextNoise();
|
||||||
|
samples.q()[x] += nextNoise();
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getFrequency()
|
||||||
|
{
|
||||||
|
return mComplexOscillator.getFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrequency(double frequency)
|
||||||
|
{
|
||||||
|
mComplexOscillator.setFrequency(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFrequency()
|
||||||
|
{
|
||||||
|
return mComplexOscillator.hasFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getSampleRate()
|
||||||
|
{
|
||||||
|
return mComplexOscillator.getSampleRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSampleRate(double sampleRate)
|
||||||
|
{
|
||||||
|
mComplexOscillator.setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled()
|
||||||
|
{
|
||||||
|
return mComplexOscillator.isEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is 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.psk.pll;
|
package io.github.dsheirer.dsp.psk.pll;
|
||||||
|
|
||||||
@@ -101,7 +98,9 @@ public class FrequencyCorrectionSyncMonitor implements ISyncDetectListener, IFre
|
|||||||
@Override
|
@Override
|
||||||
public void processFrequencyError(long frequencyError)
|
public void processFrequencyError(long frequencyError)
|
||||||
{
|
{
|
||||||
//Only rebroadcast the frequency error if the sync count is more than 2
|
mFeedbackDecoder.broadcast(SourceEvent.pllFrequencyMeasurement(frequencyError));
|
||||||
|
|
||||||
|
//Only rebroadcast as a frequency error measurement if the sync count is more than 2
|
||||||
if(mSyncCount > 2)
|
if(mSyncCount > 2)
|
||||||
{
|
{
|
||||||
mFeedbackDecoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
|
mFeedbackDecoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is 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;
|
package io.github.dsheirer.edac;
|
||||||
@@ -34,7 +31,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class BerlekempMassey
|
public class BerlekempMassey
|
||||||
{
|
{
|
||||||
public final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
|
private final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
|
||||||
|
|
||||||
/* Golay field size GF( 2 ** MM ) */
|
/* Golay field size GF( 2 ** MM ) */
|
||||||
private int MM;
|
private int MM;
|
||||||
|
|||||||
66
src/main/java/io/github/dsheirer/edac/Checksum_5_DMR.java
Normal file
66
src/main/java/io/github/dsheirer/edac/Checksum_5_DMR.java
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the 5-bit Checksum error detection algorithm specified in TS 102 361-1 paragraph B.3.11
|
||||||
|
*/
|
||||||
|
public class Checksum_5_DMR
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(Checksum_5_DMR.class);
|
||||||
|
|
||||||
|
private static final int[] LC_0 = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] LC_1 = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
|
private static final int[] LC_2 = {16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] LC_3 = {24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] LC_4 = {32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
|
private static final int[] LC_5 = {40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] LC_6 = {48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] LC_7 = {56, 57, 58, 59, 60, 61, 62, 63};
|
||||||
|
private static final int[] LC_8 = {64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
|
private static final int[] CHECKSUM = {72, 73, 74, 75, 76};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the 77-bit Full Link Control message is valid.
|
||||||
|
* @param message to check
|
||||||
|
* @return true if the checksum matches.
|
||||||
|
*/
|
||||||
|
public static boolean isValid(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
int accumulator = message.getInt(LC_0);
|
||||||
|
accumulator += message.getInt(LC_1);
|
||||||
|
accumulator += message.getInt(LC_2);
|
||||||
|
accumulator += message.getInt(LC_3);
|
||||||
|
accumulator += message.getInt(LC_4);
|
||||||
|
accumulator += message.getInt(LC_5);
|
||||||
|
accumulator += message.getInt(LC_6);
|
||||||
|
accumulator += message.getInt(LC_7);
|
||||||
|
accumulator += message.getInt(LC_8);
|
||||||
|
|
||||||
|
accumulator = accumulator % 31;
|
||||||
|
int checksum = message.getInt(CHECKSUM);
|
||||||
|
|
||||||
|
return accumulator == checksum;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,12 +20,16 @@
|
|||||||
package io.github.dsheirer.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Berlecamp-Massey decoder for Reed Solomon RS(12,9,4) protected messages using DMR generator polynomial
|
* 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
|
public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
||||||
{
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(ReedSolomon_12_9_4_DMR.class);
|
||||||
|
|
||||||
private static final int[] FLC_CODEWORD_0 = {0, 1, 2, 3, 4, 5, 6, 7};
|
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_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_2 = {16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
@@ -56,16 +60,16 @@ public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
|||||||
*
|
*
|
||||||
* @param message to be corrected.
|
* @param message to be corrected.
|
||||||
* @param crcMask as defined in the TS 102-361-1 Table B.21
|
* @param crcMask as defined in the TS 102-361-1 Table B.21
|
||||||
* @return true if the message was corrected.
|
* @return true if the message is correct or if the message was corrected.
|
||||||
*/
|
*/
|
||||||
public boolean correctFullLinkControl(CorrectedBinaryMessage message, int crcMask)
|
public boolean correctFullLinkControl(CorrectedBinaryMessage message, int crcMask)
|
||||||
{
|
{
|
||||||
int[] input = new int[255];
|
int[] input = new int[255];
|
||||||
int[] output = new int[255];
|
int[] output = new int[255];
|
||||||
|
|
||||||
input[0] = message.getInt(FLC_PARITY_2) ^ crcMask;
|
input[0] = (message.getInt(FLC_PARITY_2) ^ crcMask);
|
||||||
input[1] = message.getInt(FLC_PARITY_1) ^ crcMask;
|
input[1] = (message.getInt(FLC_PARITY_1) ^ crcMask);
|
||||||
input[2] = message.getInt(FLC_PARITY_0) ^ crcMask;
|
input[2] = (message.getInt(FLC_PARITY_0) ^ crcMask);
|
||||||
input[3] = message.getInt(FLC_CODEWORD_8);
|
input[3] = message.getInt(FLC_CODEWORD_8);
|
||||||
input[4] = message.getInt(FLC_CODEWORD_7);
|
input[4] = message.getInt(FLC_CODEWORD_7);
|
||||||
input[5] = message.getInt(FLC_CODEWORD_6);
|
input[5] = message.getInt(FLC_CODEWORD_6);
|
||||||
@@ -106,4 +110,40 @@ public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
int[] output = new int[255];
|
||||||
|
int[] input = new int[255];
|
||||||
|
input[0] = 247;
|
||||||
|
input[1] = 236;
|
||||||
|
input[2] = 171;
|
||||||
|
input[3] = 24;
|
||||||
|
input[4] = 46;
|
||||||
|
input[5] = 177;
|
||||||
|
input[6] = 246;
|
||||||
|
input[7] = 205;
|
||||||
|
input[8] = 232;
|
||||||
|
input[9] = 32;
|
||||||
|
input[10] = 124;
|
||||||
|
input[11] = 0;
|
||||||
|
|
||||||
|
// input[254] = 247;
|
||||||
|
// input[253] = 236;
|
||||||
|
// input[252] = 171;
|
||||||
|
// input[251] = 24;
|
||||||
|
// input[250] = 46;
|
||||||
|
// input[249] = 177;
|
||||||
|
// input[248] = 246;
|
||||||
|
// input[247] = 205;
|
||||||
|
// input[246] = 232;
|
||||||
|
// input[245] = 32;
|
||||||
|
// input[244] = 124;
|
||||||
|
// input[243] = 0;
|
||||||
|
|
||||||
|
ReedSolomon_12_9_4_DMR rs = new ReedSolomon_12_9_4_DMR();
|
||||||
|
boolean irrecoverable = rs.decode(input, output);
|
||||||
|
|
||||||
|
mLog.info("Irrecoverable: " + irrecoverable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,8 @@ import io.github.dsheirer.alias.AliasModel;
|
|||||||
import io.github.dsheirer.controller.channel.map.ChannelMap;
|
import io.github.dsheirer.controller.channel.map.ChannelMap;
|
||||||
import io.github.dsheirer.controller.channel.map.ChannelRange;
|
import io.github.dsheirer.controller.channel.map.ChannelRange;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
|
import io.github.dsheirer.gui.dmr.DMRRecordingViewer;
|
||||||
|
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
|
||||||
import io.github.dsheirer.gui.icon.IconManager;
|
import io.github.dsheirer.gui.icon.IconManager;
|
||||||
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
||||||
import io.github.dsheirer.gui.playlist.PlaylistEditor;
|
import io.github.dsheirer.gui.playlist.PlaylistEditor;
|
||||||
@@ -65,6 +67,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
public static final String USER_PREFERENCES_EDITOR = "preferences";
|
public static final String USER_PREFERENCES_EDITOR = "preferences";
|
||||||
public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog";
|
public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog";
|
||||||
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
|
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
|
||||||
|
public static final String STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER = "dmr.message.viewer";
|
||||||
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
|
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
|
||||||
public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor";
|
public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor";
|
||||||
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
|
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
|
||||||
@@ -79,12 +82,14 @@ public class JavaFxWindowManager extends Application
|
|||||||
private TunerManager mTunerManager;
|
private TunerManager mTunerManager;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private UserPreferencesEditor mUserPreferencesEditor;
|
private UserPreferencesEditor mUserPreferencesEditor;
|
||||||
|
private DMRRecordingViewer mDmrRecordingViewer;
|
||||||
|
|
||||||
private Stage mChannelMapStage;
|
private Stage mChannelMapStage;
|
||||||
private Stage mIconManagerStage;
|
private Stage mIconManagerStage;
|
||||||
private Stage mJmbeEditorStage;
|
private Stage mJmbeEditorStage;
|
||||||
private Stage mPlaylistStage;
|
private Stage mPlaylistStage;
|
||||||
private Stage mUserPreferencesStage;
|
private Stage mUserPreferencesStage;
|
||||||
|
private Stage mDmrRecordingViewerStage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance. Note: this constructor is used for Swing applications.
|
* Constructs an instance. Note: this constructor is used for Swing applications.
|
||||||
@@ -189,6 +194,34 @@ public class JavaFxWindowManager extends Application
|
|||||||
return new CalibrationDialog(userPreferences);
|
return new CalibrationDialog(userPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stage for the DMR Message Viewer
|
||||||
|
*/
|
||||||
|
public Stage getDmrRecordingViewerStage()
|
||||||
|
{
|
||||||
|
if(mDmrRecordingViewerStage == null)
|
||||||
|
{
|
||||||
|
createJFXPanel();
|
||||||
|
Scene scene = new Scene(getDmrRecordingViewer(), 1100, 800);
|
||||||
|
mDmrRecordingViewerStage = new Stage();
|
||||||
|
mDmrRecordingViewerStage.setTitle("sdrtrunk - DMR Recording Viewer");
|
||||||
|
mDmrRecordingViewerStage.setScene(scene);
|
||||||
|
mUserPreferences.getJavaFxPreferences().monitor(mDmrRecordingViewerStage, STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDmrRecordingViewerStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DMRRecordingViewer getDmrRecordingViewer()
|
||||||
|
{
|
||||||
|
if(mDmrRecordingViewer == null)
|
||||||
|
{
|
||||||
|
mDmrRecordingViewer = new DMRRecordingViewer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDmrRecordingViewer;
|
||||||
|
}
|
||||||
|
|
||||||
public Stage getIconManagerStage()
|
public Stage getIconManagerStage()
|
||||||
{
|
{
|
||||||
if(mIconManagerStage == null)
|
if(mIconManagerStage == null)
|
||||||
@@ -416,6 +449,19 @@ public class JavaFxWindowManager extends Application
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a channel map editor request
|
||||||
|
*/
|
||||||
|
@Subscribe
|
||||||
|
public void process(final ViewDmrRecordingViewerRequest request)
|
||||||
|
{
|
||||||
|
execute(() -> {
|
||||||
|
getDmrRecordingViewerStage().show();
|
||||||
|
getDmrRecordingViewerStage().requestFocus();
|
||||||
|
getDmrRecordingViewerStage().toFront();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception
|
public void start(Stage primaryStage) throws Exception
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,6 +32,7 @@ import io.github.dsheirer.controller.channel.ChannelAutoStartFrame;
|
|||||||
import io.github.dsheirer.controller.channel.ChannelException;
|
import io.github.dsheirer.controller.channel.ChannelException;
|
||||||
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
|
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
|
||||||
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
||||||
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
||||||
import io.github.dsheirer.gui.preference.CalibrateRequest;
|
import io.github.dsheirer.gui.preference.CalibrateRequest;
|
||||||
@@ -59,6 +60,26 @@ import io.github.dsheirer.spectrum.SpectralDisplayPanel;
|
|||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import io.github.dsheirer.util.TimeStamp;
|
import io.github.dsheirer.util.TimeStamp;
|
||||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||||
|
import java.awt.AWTException;
|
||||||
|
import java.awt.Desktop;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.GraphicsEnvironment;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Robot;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import jiconfont.icons.font_awesome.FontAwesome;
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
@@ -80,26 +101,6 @@ import javax.swing.UIManager;
|
|||||||
import javax.swing.event.MenuEvent;
|
import javax.swing.event.MenuEvent;
|
||||||
import javax.swing.event.MenuListener;
|
import javax.swing.event.MenuListener;
|
||||||
import javax.swing.plaf.metal.MetalLookAndFeel;
|
import javax.swing.plaf.metal.MetalLookAndFeel;
|
||||||
import java.awt.AWTException;
|
|
||||||
import java.awt.Desktop;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.EventQueue;
|
|
||||||
import java.awt.GraphicsEnvironment;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.awt.Robot;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.awt.event.WindowAdapter;
|
|
||||||
import java.awt.event.WindowEvent;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class SDRTrunk implements Listener<TunerEvent>
|
public class SDRTrunk implements Listener<TunerEvent>
|
||||||
{
|
{
|
||||||
@@ -413,9 +414,11 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
|
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
|
||||||
viewMenu.add(viewPlaylistItem);
|
viewMenu.add(viewPlaylistItem);
|
||||||
|
|
||||||
JMenuItem preferencesItem = new JMenuItem("User Preferences");
|
viewMenu.add(new JSeparator());
|
||||||
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
|
|
||||||
viewMenu.add(preferencesItem);
|
JMenuItem dmrMessageViewerMenu = new JMenuItem("DMR Recording Viewer");
|
||||||
|
dmrMessageViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewDmrRecordingViewerRequest()));
|
||||||
|
viewMenu.add(dmrMessageViewerMenu);
|
||||||
|
|
||||||
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
||||||
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||||
@@ -436,15 +439,19 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
mLog.error("Couldn't open file explorer");
|
mLog.error("Couldn't open file explorer");
|
||||||
|
|
||||||
JOptionPane.showMessageDialog(mMainGui,
|
JOptionPane.showMessageDialog(mMainGui,
|
||||||
"Can't launch file explorer - files are located at: " +
|
"Can't launch file explorer - files are located at: " +
|
||||||
getHomePath().toString(),
|
getHomePath().toString(),
|
||||||
"Can't launch file explorer",
|
"Can't launch file explorer",
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
viewMenu.add(logFilesMenu);
|
viewMenu.add(logFilesMenu);
|
||||||
|
|
||||||
|
JMenuItem preferencesItem = new JMenuItem("User Preferences");
|
||||||
|
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
|
||||||
|
viewMenu.add(preferencesItem);
|
||||||
|
|
||||||
viewMenu.add(new JSeparator());
|
viewMenu.add(new JSeparator());
|
||||||
viewMenu.add(new TunersMenu());
|
viewMenu.add(new TunersMenu());
|
||||||
viewMenu.add(new JSeparator());
|
viewMenu.add(new JSeparator());
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,6 +20,7 @@ package io.github.dsheirer.gui.channelizer;
|
|||||||
|
|
||||||
import io.github.dsheirer.buffer.FloatNativeBuffer;
|
import io.github.dsheirer.buffer.FloatNativeBuffer;
|
||||||
import io.github.dsheirer.buffer.INativeBuffer;
|
import io.github.dsheirer.buffer.INativeBuffer;
|
||||||
|
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
import io.github.dsheirer.settings.SettingsManager;
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
@@ -36,6 +37,12 @@ import io.github.dsheirer.spectrum.ComplexDftProcessor;
|
|||||||
import io.github.dsheirer.spectrum.DFTSize;
|
import io.github.dsheirer.spectrum.DFTSize;
|
||||||
import io.github.dsheirer.spectrum.SpectrumPanel;
|
import io.github.dsheirer.spectrum.SpectrumPanel;
|
||||||
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -50,12 +57,6 @@ import javax.swing.JToggleButton;
|
|||||||
import javax.swing.SpinnerNumberModel;
|
import javax.swing.SpinnerNumberModel;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.EventQueue;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ChannelizerViewer2 extends JFrame
|
public class ChannelizerViewer2 extends JFrame
|
||||||
{
|
{
|
||||||
@@ -194,9 +195,9 @@ public class ChannelizerViewer2 extends JFrame
|
|||||||
|
|
||||||
getTopChannelArrayPanel().addChannel(tunerChannel);
|
getTopChannelArrayPanel().addChannel(tunerChannel);
|
||||||
}
|
}
|
||||||
catch(Exception exception)
|
catch(Exception e1)
|
||||||
{
|
{
|
||||||
mLog.error("Can't parse frequency from value: " + value);
|
mLog.error("Can't parse frequency from value: " + value, e1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,9 +230,9 @@ public class ChannelizerViewer2 extends JFrame
|
|||||||
|
|
||||||
getBottomChannelArrayPanel().addChannel(tunerChannel);
|
getBottomChannelArrayPanel().addChannel(tunerChannel);
|
||||||
}
|
}
|
||||||
catch(Exception exception)
|
catch(Exception e1)
|
||||||
{
|
{
|
||||||
mLog.error("Can't parse frequency from value: " + value);
|
mLog.error("Can't parse frequency from value: " + value, e1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,6 +389,26 @@ public class ChannelizerViewer2 extends JFrame
|
|||||||
mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()));
|
mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(mSource instanceof PolyphaseChannelSource pcs)
|
||||||
|
{ List<Integer> indexes = pcs.getOutputProcessorIndexes();
|
||||||
|
double sRate = pcs.getSampleRate();
|
||||||
|
long indexCenterFrequency = pcs.getIndexCenterFrequency();
|
||||||
|
long appliedFrequencyOffset = pcs.getFrequencyOffset();
|
||||||
|
long requestedCenterFrequency = pcs.getFrequency();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Polyphase Channel - Bandwidth: ").append(sRate);
|
||||||
|
sb.append(" Channel/Requested/Offset: ").append(indexCenterFrequency);
|
||||||
|
sb.append("/").append(requestedCenterFrequency);
|
||||||
|
sb.append("/").append(appliedFrequencyOffset);
|
||||||
|
sb.append(" Indexes: ").append(indexes);
|
||||||
|
sb.append(" Tuner SR:").append(pcs.getTunerSampleRate());
|
||||||
|
sb.append(" Tuner CF:").append(pcs.getTunerCenterFrequency());
|
||||||
|
mLog.info(sb.toString());
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
mSource.start();
|
mSource.start();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
479
src/main/java/io/github/dsheirer/gui/dmr/DMRRecordingViewer.java
Normal file
479
src/main/java/io/github/dsheirer/gui/dmr/DMRRecordingViewer.java
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.gui.dmr;
|
||||||
|
|
||||||
|
import io.github.dsheirer.message.IMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRMessageFramer;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||||
|
import io.github.dsheirer.record.binary.BinaryReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.collections.transformation.FilteredList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility application to load and view a DMR .bits recording file with the messages fully parsed.
|
||||||
|
*/
|
||||||
|
public class DMRRecordingViewer extends VBox
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(DMRRecordingViewer.class);
|
||||||
|
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory";
|
||||||
|
private Preferences mPreferences = Preferences.userNodeForPackage(DMRRecordingViewer.class);
|
||||||
|
private Scene mScene;
|
||||||
|
private Button mSelectFileButton;
|
||||||
|
private Label mSelectedFileLabel;
|
||||||
|
private TableView<IMessage> mMessageTableView;
|
||||||
|
private ObservableList<IMessage> mMessages = FXCollections.observableArrayList();
|
||||||
|
private FilteredList<IMessage> mFilteredMessages = new FilteredList<>(mMessages);
|
||||||
|
private CheckBox mShowTS0;
|
||||||
|
private CheckBox mShowTS1;
|
||||||
|
private CheckBox mShowTS2;
|
||||||
|
private CheckBox mUseCompressedTalkgroups;
|
||||||
|
private TextField mSearchText;
|
||||||
|
private TextField mFindText;
|
||||||
|
private Button mFindButton;
|
||||||
|
private Button mFindNextButton;
|
||||||
|
|
||||||
|
public DMRRecordingViewer()
|
||||||
|
{
|
||||||
|
setPadding(new Insets(5));
|
||||||
|
setSpacing(5);
|
||||||
|
|
||||||
|
HBox fileBox = new HBox();
|
||||||
|
fileBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
fileBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
fileBox.setSpacing(5);
|
||||||
|
HBox.setHgrow(getSelectFileButton(), Priority.NEVER);
|
||||||
|
HBox.setHgrow(getSelectedFileLabel(), Priority.ALWAYS);
|
||||||
|
getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER);
|
||||||
|
|
||||||
|
HBox compressedBox = new HBox();
|
||||||
|
compressedBox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
compressedBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
compressedBox.getChildren().addAll(getUseCompressedTalkgroups());
|
||||||
|
HBox.setHgrow(compressedBox, Priority.ALWAYS);
|
||||||
|
|
||||||
|
fileBox.getChildren().addAll(getSelectFileButton(), getSelectedFileLabel(), compressedBox);
|
||||||
|
|
||||||
|
HBox filterBox = new HBox();
|
||||||
|
filterBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
filterBox.setAlignment(Pos.BASELINE_CENTER);
|
||||||
|
filterBox.setSpacing(5);
|
||||||
|
|
||||||
|
Label showLabel = new Label("Show:");
|
||||||
|
HBox.setMargin(showLabel, new Insets(0,0,0,15));
|
||||||
|
Label searchLabel = new Label("Message Filter:");
|
||||||
|
HBox.setMargin(searchLabel, new Insets(0,0,0,15));
|
||||||
|
Label findLabel = new Label("Find:");
|
||||||
|
|
||||||
|
HBox.setHgrow(getFindText(), Priority.ALWAYS);
|
||||||
|
HBox.setHgrow(getSearchText(), Priority.ALWAYS);
|
||||||
|
|
||||||
|
filterBox.getChildren().addAll(findLabel, getFindText(), getFindButton(), getFindNextButton(), searchLabel,
|
||||||
|
getSearchText(), showLabel, getShowTS0(), getShowTS1(), getShowTS2());
|
||||||
|
|
||||||
|
VBox.setVgrow(fileBox, Priority.NEVER);
|
||||||
|
VBox.setVgrow(filterBox, Priority.NEVER);
|
||||||
|
VBox.setVgrow(getMessageTableView(), Priority.ALWAYS);
|
||||||
|
|
||||||
|
getChildren().addAll(fileBox, filterBox, getMessageTableView());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the recording file and loads the content into the viewer
|
||||||
|
* @param file containing a .bits recording of decoded DMR data.
|
||||||
|
*/
|
||||||
|
private void load(File file)
|
||||||
|
{
|
||||||
|
if(file != null && file.exists())
|
||||||
|
{
|
||||||
|
getSelectedFileLabel().setText(file.getName());
|
||||||
|
mMessages.clear();
|
||||||
|
|
||||||
|
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
|
||||||
|
DecodeConfigDMR config = new DecodeConfigDMR();
|
||||||
|
if(getUseCompressedTalkgroups().isSelected())
|
||||||
|
{
|
||||||
|
config.setUseCompressedTalkgroups(true);
|
||||||
|
}
|
||||||
|
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
|
||||||
|
messageFramer.setListener(messageProcessor);
|
||||||
|
messageProcessor.setMessageListener(message -> mMessages.add(message));
|
||||||
|
|
||||||
|
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
|
||||||
|
{
|
||||||
|
while(reader.hasNext())
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = reader.next();
|
||||||
|
messageFramer.receive(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception ioe)
|
||||||
|
{
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the filter(s) applies to the list of messages
|
||||||
|
*/
|
||||||
|
private void updateFilters()
|
||||||
|
{
|
||||||
|
Predicate<IMessage> timeslotPredicate = message ->
|
||||||
|
(getShowTS0().isSelected() && (message.getTimeslot() == 0)) ||
|
||||||
|
(getShowTS1().isSelected() && (message.getTimeslot() == 1)) ||
|
||||||
|
(getShowTS2().isSelected() && (message.getTimeslot() == 2));
|
||||||
|
|
||||||
|
String filterText = getSearchText().getText();
|
||||||
|
|
||||||
|
if(filterText == null || filterText.isEmpty())
|
||||||
|
{
|
||||||
|
mFilteredMessages.setPredicate(timeslotPredicate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Predicate<IMessage> textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase());
|
||||||
|
mFilteredMessages.setPredicate(timeslotPredicate.and(textPredicate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and selects the first row containing the text argument.
|
||||||
|
* @param text to search for.
|
||||||
|
*/
|
||||||
|
private void find(String text)
|
||||||
|
{
|
||||||
|
if(text != null && !text.isEmpty())
|
||||||
|
{
|
||||||
|
for(IMessage message: mFilteredMessages)
|
||||||
|
{
|
||||||
|
if(message.toString().toLowerCase().contains(text.toLowerCase()))
|
||||||
|
{
|
||||||
|
getMessageTableView().getSelectionModel().select(message);
|
||||||
|
getMessageTableView().scrollTo(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and selects the first row containing the text argument, after the currently selected row.
|
||||||
|
* @param text to search for.
|
||||||
|
*/
|
||||||
|
private void findNext(String text)
|
||||||
|
{
|
||||||
|
if(text != null && !text.isEmpty())
|
||||||
|
{
|
||||||
|
IMessage selected = getMessageTableView().getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
|
if(selected == null)
|
||||||
|
{
|
||||||
|
find(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int row = mFilteredMessages.indexOf(selected);
|
||||||
|
|
||||||
|
for(int x = row + 1; x < mFilteredMessages.size(); x++)
|
||||||
|
{
|
||||||
|
if(x < mFilteredMessages.size())
|
||||||
|
{
|
||||||
|
IMessage message = mFilteredMessages.get(x);
|
||||||
|
|
||||||
|
if(message.toString().toLowerCase().contains(text.toLowerCase()))
|
||||||
|
{
|
||||||
|
getMessageTableView().getSelectionModel().select(message);
|
||||||
|
getMessageTableView().scrollTo(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List view control with DMR messages
|
||||||
|
*/
|
||||||
|
private TableView<IMessage> getMessageTableView()
|
||||||
|
{
|
||||||
|
if(mMessageTableView == null)
|
||||||
|
{
|
||||||
|
mMessageTableView = new TableView<>(mFilteredMessages);
|
||||||
|
|
||||||
|
TableColumn timestampColumn = new TableColumn();
|
||||||
|
timestampColumn.setPrefWidth(110);
|
||||||
|
timestampColumn.setText("Time");
|
||||||
|
timestampColumn.setCellValueFactory(new PropertyValueFactory<>("timestamp"));
|
||||||
|
|
||||||
|
TableColumn timeslotColumn = new TableColumn();
|
||||||
|
timeslotColumn.setPrefWidth(35);
|
||||||
|
timeslotColumn.setText("TS");
|
||||||
|
timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot"));
|
||||||
|
|
||||||
|
TableColumn validColumn = new TableColumn();
|
||||||
|
validColumn.setPrefWidth(50);
|
||||||
|
validColumn.setText("Valid");
|
||||||
|
validColumn.setCellValueFactory(new PropertyValueFactory<>("valid"));
|
||||||
|
|
||||||
|
TableColumn messageColumn = new TableColumn();
|
||||||
|
messageColumn.setPrefWidth(1000);
|
||||||
|
messageColumn.setText("Message");
|
||||||
|
messageColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures, ObservableValue>) param -> {
|
||||||
|
SimpleStringProperty property = new SimpleStringProperty();
|
||||||
|
if(param.getValue() instanceof IMessage message)
|
||||||
|
{
|
||||||
|
property.set(message.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return property;
|
||||||
|
});
|
||||||
|
|
||||||
|
mMessageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMessageTableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File selection button
|
||||||
|
* @return button
|
||||||
|
*/
|
||||||
|
private Button getSelectFileButton()
|
||||||
|
{
|
||||||
|
if(mSelectFileButton == null)
|
||||||
|
{
|
||||||
|
mSelectFileButton = new Button("Select ...");
|
||||||
|
mSelectFileButton.onActionProperty().set(event -> {
|
||||||
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle("Select DMR .bits Recording");
|
||||||
|
String lastDirectory = mPreferences.get(LAST_SELECTED_DIRECTORY, null);
|
||||||
|
if(lastDirectory != null)
|
||||||
|
{
|
||||||
|
File file = new File(lastDirectory);
|
||||||
|
if(file.exists() && file.isDirectory())
|
||||||
|
{
|
||||||
|
fileChooser.setInitialDirectory(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("sdrtrunk bits recording", "*.bits"));
|
||||||
|
File selected = fileChooser.showOpenDialog(getScene().getWindow());
|
||||||
|
|
||||||
|
if(selected != null)
|
||||||
|
{
|
||||||
|
mLog.info("Last Selected Directory updated to [" + selected.getParent() +"]");
|
||||||
|
mPreferences.put(LAST_SELECTED_DIRECTORY, selected.getParent());
|
||||||
|
load(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSelectFileButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected file path label.
|
||||||
|
*/
|
||||||
|
private Label getSelectedFileLabel()
|
||||||
|
{
|
||||||
|
if(mSelectedFileLabel == null)
|
||||||
|
{
|
||||||
|
mSelectedFileLabel = new Label(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSelectedFileLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box to apply filter to show/hide TS0 messages
|
||||||
|
* @return check box control
|
||||||
|
*/
|
||||||
|
private CheckBox getShowTS0()
|
||||||
|
{
|
||||||
|
if(mShowTS0 == null)
|
||||||
|
{
|
||||||
|
mShowTS0 = new CheckBox("TS0");
|
||||||
|
mShowTS0.setSelected(true);
|
||||||
|
mShowTS0.setOnAction(event -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mShowTS0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box to apply filter to show/hide TS0 messages
|
||||||
|
* @return check box control
|
||||||
|
*/
|
||||||
|
private CheckBox getShowTS1()
|
||||||
|
{
|
||||||
|
if(mShowTS1 == null)
|
||||||
|
{
|
||||||
|
mShowTS1 = new CheckBox("TS1");
|
||||||
|
mShowTS1.setSelected(true);
|
||||||
|
mShowTS1.setOnAction(event -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mShowTS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box to apply filter to show/hide TS0 messages
|
||||||
|
* @return check box control
|
||||||
|
*/
|
||||||
|
private CheckBox getShowTS2()
|
||||||
|
{
|
||||||
|
if(mShowTS2 == null)
|
||||||
|
{
|
||||||
|
mShowTS2 = new CheckBox("TS2");
|
||||||
|
mShowTS2.setSelected(true);
|
||||||
|
mShowTS2.setOnAction(event -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mShowTS2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search text filter box
|
||||||
|
* @return text control for entering search text
|
||||||
|
*/
|
||||||
|
private TextField getSearchText()
|
||||||
|
{
|
||||||
|
if(mSearchText == null)
|
||||||
|
{
|
||||||
|
mSearchText = new TextField();
|
||||||
|
mSearchText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSearchText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text box for find text
|
||||||
|
*/
|
||||||
|
private TextField getFindText()
|
||||||
|
{
|
||||||
|
if(mFindText == null)
|
||||||
|
{
|
||||||
|
mFindText = new TextField();
|
||||||
|
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFindText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find button to search for the text in the find text box.
|
||||||
|
* @return button
|
||||||
|
*/
|
||||||
|
private Button getFindButton()
|
||||||
|
{
|
||||||
|
if(mFindButton == null)
|
||||||
|
{
|
||||||
|
mFindButton = new Button("Find");
|
||||||
|
mFindButton.setOnAction(event -> find(getFindText().getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFindButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find next button to search for the text in the find text box.
|
||||||
|
* @return button
|
||||||
|
*/
|
||||||
|
private Button getFindNextButton()
|
||||||
|
{
|
||||||
|
if(mFindNextButton == null)
|
||||||
|
{
|
||||||
|
mFindNextButton = new Button("Next");
|
||||||
|
mFindNextButton.setOnAction(event -> findNext(getFindText().getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFindNextButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Hytera Tier 3 Compressed Talkgroups
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private CheckBox getUseCompressedTalkgroups()
|
||||||
|
{
|
||||||
|
if(mUseCompressedTalkgroups == null)
|
||||||
|
{
|
||||||
|
mUseCompressedTalkgroups = new CheckBox("Use Compressed Talkgroups");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mUseCompressedTalkgroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
Application viewer = new Application()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) throws Exception
|
||||||
|
{
|
||||||
|
Scene scene = new Scene(new DMRRecordingViewer(), 1100, 800);
|
||||||
|
primaryStage.setTitle("DMR Recording Viewer");
|
||||||
|
primaryStage.setScene(scene);
|
||||||
|
primaryStage.show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Runnable r = () -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
viewer.start(new Stage());
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error starting DMR recording viewer application", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Platform.startup(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.gui.dmr;
|
||||||
|
|
||||||
|
import io.github.dsheirer.gui.JavaFxWindowRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to show the DMR recording viewer
|
||||||
|
*/
|
||||||
|
public class ViewDmrRecordingViewerRequest extends JavaFxWindowRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public ViewDmrRecordingViewerRequest() {}
|
||||||
|
}
|
||||||
@@ -217,6 +217,9 @@ public class RadioIdEditor extends IdentifierEditor<Radio>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.warn("Unable to find radio id editor for protocol [" + protocol + "] and format [" + integerFormat +
|
||||||
|
"] - using default editor");
|
||||||
|
|
||||||
//Use a default instance
|
//Use a default instance
|
||||||
for(RadioDetail detail: mRadioDetails)
|
for(RadioDetail detail: mRadioDetails)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.warn("Couldn't find radio ID detail for protocol [" + getItem().getProtocol() +
|
mLog.warn("Couldn't find radio ID detail for protocol [" + getItem().getProtocol() +
|
||||||
"] and format [" + format + "]");
|
"] and format [" + format + "]");
|
||||||
getFormatLabel().setText(" ");
|
getFormatLabel().setText(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,6 +267,9 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.warn("Unable to find radio id range editor for protocol [" + protocol + "] and format [" + integerFormat +
|
||||||
|
"] - using default editor");
|
||||||
|
|
||||||
//Use a default instance
|
//Use a default instance
|
||||||
for(RadioIdDetail detail: mRadioDetails)
|
for(RadioIdDetail detail: mRadioDetails)
|
||||||
{
|
{
|
||||||
@@ -287,20 +290,23 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
|
|||||||
{
|
{
|
||||||
List<RadioIdDetail> details = new ArrayList<>();
|
List<RadioIdDetail> details = new ArrayList<>();
|
||||||
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
||||||
new IntegerFormatter(0,0xFFFFFF), "Format: 0 - 16777215"));
|
new IntegerFormatter(0,0xFFFFFF), "Format: 0 - 16777215"));
|
||||||
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
||||||
new HexFormatter(0,0xFFFFFF), "Format: 0 - FFFFFF"));
|
new HexFormatter(0,0xFFFFFF), "Format: 0 - FFFFFF"));
|
||||||
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
|
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
|
||||||
new IntegerFormatter(0,0x7FFFFF), "Format: 0 - 8388607"));
|
new IntegerFormatter(0,0x7FFFFF), "Format: 0 - 8388607"));
|
||||||
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),
|
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),
|
||||||
new HexFormatter(0,0x7FFFFF), "Format: 0 - 7FFFFF"));
|
new HexFormatter(0,0x7FFFFF), "Format: 0 - 7FFFFF"));
|
||||||
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||||
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
||||||
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
||||||
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
|
details.add(new RadioIdDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||||
|
new IntegerFormatter(0,16777215), "Format: 0 - 16777215"));
|
||||||
|
details.add(new RadioIdDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
||||||
|
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
|
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
|
||||||
"] and format [" + format + "]");
|
"] and format [" + format + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormatLabel().setText(talkgroupDetail.getTooltip());
|
getFormatLabel().setText(talkgroupDetail.getTooltip());
|
||||||
@@ -219,6 +219,9 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.warn("Unable to find talkgroup editor for protocol [" + protocol + "] and format [" + integerFormat +
|
||||||
|
"] - using default editor");
|
||||||
|
|
||||||
//Use a default instance
|
//Use a default instance
|
||||||
for(TalkgroupDetail detail: mTalkgroupDetails)
|
for(TalkgroupDetail detail: mTalkgroupDetails)
|
||||||
{
|
{
|
||||||
@@ -236,37 +239,37 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
|||||||
{
|
{
|
||||||
mTalkgroupDetails.clear();
|
mTalkgroupDetails.clear();
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
|
||||||
"Format: 0 - 65535"));
|
"Format: 0 - 65535"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
||||||
"Format: 0 - FFFF"));
|
"Format: 0 - FFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFFFF),
|
||||||
"Format: 0 - 16,777,215"));
|
"Format: 1 - 16,777,215"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFFFF),
|
||||||
"Format: 0 - FFFFFF"));
|
"Format: 1 - FFFFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
||||||
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
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),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
|
||||||
"Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
|
"Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||||
"Format: 0 - 65535"));
|
"Format: 0 - 65535"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||||
"Format: 0 - FFFF"));
|
"Format: 0 - FFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
||||||
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFF),
|
||||||
"Format: 1 - 65535"));
|
"Format: 1 - 65535"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFF),
|
||||||
"Format: 1 - FFFF"));
|
"Format: 1 - FFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||||
"Format: 0 - 65535"));
|
"Format: 0 - 65535"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||||
"Format: 0 - FFFF"));
|
"Format: 0 - FFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||||
"Format: 0 - FFFFFF"));
|
"Format: 0 - FFFFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
||||||
"Format: 0 - FFFFFF"));
|
"Format: 0 - FFFFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
||||||
"Format: 0 - FFFFFF"));
|
"Format: 0 - FFFFFF"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TalkgroupDetail
|
public static class TalkgroupDetail
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
|
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
|
||||||
"] and format [" + format + "]");
|
"] and format [" + format + "]");
|
||||||
getFormatLabel().setText(" ");
|
getFormatLabel().setText(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,6 +269,9 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.warn("Unable to find talkgroup range editor for protocol [" + protocol + "] and format [" + integerFormat +
|
||||||
|
"] - using default editor");
|
||||||
|
|
||||||
//Use a default instance
|
//Use a default instance
|
||||||
for(TalkgroupDetail detail: mTalkgroupDetails)
|
for(TalkgroupDetail detail: mTalkgroupDetails)
|
||||||
{
|
{
|
||||||
@@ -289,31 +292,35 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
|
|||||||
{
|
{
|
||||||
List<TalkgroupDetail> details = new ArrayList<>();
|
List<TalkgroupDetail> details = new ArrayList<>();
|
||||||
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
|
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
|
||||||
new IntegerFormatter(0,65535), "Format: 0 - 65535"));
|
new IntegerFormatter(0,65535), "Format: 0 - 65535"));
|
||||||
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
||||||
new HexFormatter(0,65535), "Format: 0 - FFFF"));
|
new HexFormatter(0,65535), "Format: 0 - FFFF"));
|
||||||
details.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
details.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
||||||
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
||||||
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
||||||
details.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
|
details.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
|
||||||
new LtrFormatter(0,0x3FFF), "Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
|
new LtrFormatter(0,0x3FFF), "Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
|
||||||
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||||
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
||||||
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||||
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
||||||
details.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
details.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
||||||
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
||||||
"Format: PPP-IIII = Prefix (0-127), Ident (1-8191)"));
|
"Format: PPP-IIII = Prefix (0-127), Ident (1-8191)"));
|
||||||
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||||
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
||||||
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||||
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
||||||
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||||
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new IntegerFormatter(0,16777215), "Format: 0 - 16777215"));
|
||||||
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
||||||
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
||||||
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
|
details.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(1,16777215),
|
||||||
|
new IntegerFormatter(1,16777215), "Format: 1 - 16777215"));
|
||||||
|
details.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0XFFFFFF),
|
||||||
|
new HexFormatter(1, 0xFFFFFF), "Format: 0 - FFFFFF"));
|
||||||
|
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,6 +38,8 @@ import io.github.dsheirer.record.RecorderType;
|
|||||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
@@ -65,9 +67,6 @@ import org.controlsfx.control.ToggleSwitch;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR channel configuration editor
|
* DMR channel configuration editor
|
||||||
*/
|
*/
|
||||||
@@ -83,6 +82,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private RecordConfigurationEditor mRecordConfigurationEditor;
|
private RecordConfigurationEditor mRecordConfigurationEditor;
|
||||||
private ToggleSwitch mIgnoreDataCallsButton;
|
private ToggleSwitch mIgnoreDataCallsButton;
|
||||||
private ToggleSwitch mIgnoreCRCChecksumsButton;
|
private ToggleSwitch mIgnoreCRCChecksumsButton;
|
||||||
|
private ToggleSwitch mUseCompressedTalkgroupsToggle;
|
||||||
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||||
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
||||||
private IntegerTextField mLogicalSlotNumberField;
|
private IntegerTextField mLogicalSlotNumberField;
|
||||||
@@ -164,6 +164,14 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
GridPane.setConstraints(ignoreCRCLabel, 5, row);
|
GridPane.setConstraints(ignoreCRCLabel, 5, row);
|
||||||
gridPane.getChildren().add(ignoreCRCLabel);
|
gridPane.getChildren().add(ignoreCRCLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getUseCompressedTalkgroupsToggle(), 6, row);
|
||||||
|
gridPane.getChildren().add(getUseCompressedTalkgroupsToggle());
|
||||||
|
|
||||||
|
Label useCompressedTalkgroupsLabel = new Label("Use Compressed Talkgroups");
|
||||||
|
GridPane.setHalignment(useCompressedTalkgroupsLabel, HPos.LEFT);
|
||||||
|
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
|
||||||
|
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
|
||||||
|
|
||||||
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");
|
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.setHalignment(timeslotTableLabel, HPos.LEFT);
|
||||||
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
||||||
@@ -512,6 +520,24 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
return mIgnoreCRCChecksumsButton;
|
return mIgnoreCRCChecksumsButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use compressed talkgroups toggle switch. Let's the user select to turn on compressed talkgroups for Hytera
|
||||||
|
* Tier-III systems.
|
||||||
|
* @return toggle.
|
||||||
|
*/
|
||||||
|
private ToggleSwitch getUseCompressedTalkgroupsToggle()
|
||||||
|
{
|
||||||
|
if(mUseCompressedTalkgroupsToggle == null)
|
||||||
|
{
|
||||||
|
mUseCompressedTalkgroupsToggle = new ToggleSwitch();
|
||||||
|
mUseCompressedTalkgroupsToggle.setTooltip(new Tooltip("Use Compressed Talkgroups. This is only for Hytera Tier-III Trunked Systems"));
|
||||||
|
mUseCompressedTalkgroupsToggle.setDisable(true);
|
||||||
|
mUseCompressedTalkgroupsToggle.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mUseCompressedTalkgroupsToggle;
|
||||||
|
}
|
||||||
|
|
||||||
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
|
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
|
||||||
{
|
{
|
||||||
if(mTrafficChannelPoolSizeSpinner == null)
|
if(mTrafficChannelPoolSizeSpinner == null)
|
||||||
@@ -578,6 +604,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
{
|
{
|
||||||
getIgnoreCRCChecksumsButton().setDisable(config == null);
|
getIgnoreCRCChecksumsButton().setDisable(config == null);
|
||||||
getIgnoreDataCallsButton().setDisable(config == null);
|
getIgnoreDataCallsButton().setDisable(config == null);
|
||||||
|
getUseCompressedTalkgroupsToggle().setDisable(config == null);
|
||||||
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
|
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
|
||||||
getTimeslotTable().getItems().clear();
|
getTimeslotTable().getItems().clear();
|
||||||
getTimeslotTable().setDisable(config == null);
|
getTimeslotTable().setDisable(config == null);
|
||||||
@@ -597,6 +624,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
|
|
||||||
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
||||||
getIgnoreCRCChecksumsButton().setSelected(decodeConfig.getIgnoreCRCChecksums());
|
getIgnoreCRCChecksumsButton().setSelected(decodeConfig.getIgnoreCRCChecksums());
|
||||||
|
getUseCompressedTalkgroupsToggle().setSelected(decodeConfig.isUseCompressedTalkgroups());
|
||||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
||||||
|
|
||||||
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
|
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
|
||||||
@@ -608,6 +636,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
{
|
{
|
||||||
getIgnoreCRCChecksumsButton().setSelected(false);
|
getIgnoreCRCChecksumsButton().setSelected(false);
|
||||||
getIgnoreDataCallsButton().setSelected(false);
|
getIgnoreDataCallsButton().setSelected(false);
|
||||||
|
getUseCompressedTalkgroupsToggle().setSelected(false);
|
||||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
|
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
|
||||||
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
|
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
|
||||||
}
|
}
|
||||||
@@ -630,6 +659,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
config.setIgnoreCRCChecksums(getIgnoreCRCChecksumsButton().isSelected());
|
config.setIgnoreCRCChecksums(getIgnoreCRCChecksumsButton().isSelected());
|
||||||
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
||||||
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
||||||
|
config.setUseCompressedTalkgroups(getUseCompressedTalkgroupsToggle().isSelected());
|
||||||
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));
|
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));
|
||||||
getItem().setDecodeConfiguration(config);
|
getItem().setDecodeConfiguration(config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ import io.github.dsheirer.rrapi.type.Type;
|
|||||||
import io.github.dsheirer.rrapi.type.Voice;
|
import io.github.dsheirer.rrapi.type.Voice;
|
||||||
import io.github.dsheirer.service.radioreference.RadioReference;
|
import io.github.dsheirer.service.radioreference.RadioReference;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
@@ -55,12 +60,6 @@ import jiconfont.javafx.IconNode;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Radio Reference editor for trunked radio systems
|
* Radio Reference editor for trunked radio systems
|
||||||
*/
|
*/
|
||||||
@@ -293,7 +292,13 @@ public class SystemEditor extends VBox
|
|||||||
List<EnrichedSite> enrichedSites = new ArrayList<>();
|
List<EnrichedSite> enrichedSites = new ArrayList<>();
|
||||||
for(Site site: sites)
|
for(Site site: sites)
|
||||||
{
|
{
|
||||||
CountyInfo countyInfo = mRadioReference.getService().getCountyInfo(site.getCountyId());
|
CountyInfo countyInfo = null;
|
||||||
|
|
||||||
|
if(site.getCountyId() > 0)
|
||||||
|
{
|
||||||
|
countyInfo = mRadioReference.getService().getCountyInfo(site.getCountyId());
|
||||||
|
}
|
||||||
|
|
||||||
enrichedSites.add(new EnrichedSite(site, countyInfo));
|
enrichedSites.add(new EnrichedSite(site, countyInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.gui.power;
|
package io.github.dsheirer.gui.power;
|
||||||
|
|
||||||
import io.github.dsheirer.controller.channel.Channel;
|
import io.github.dsheirer.controller.channel.Channel;
|
||||||
|
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
|
||||||
import io.github.dsheirer.dsp.squelch.ISquelchConfiguration;
|
import io.github.dsheirer.dsp.squelch.ISquelchConfiguration;
|
||||||
import io.github.dsheirer.gui.control.DbPowerMeter;
|
import io.github.dsheirer.gui.control.DbPowerMeter;
|
||||||
import io.github.dsheirer.module.ProcessingChain;
|
import io.github.dsheirer.module.ProcessingChain;
|
||||||
import io.github.dsheirer.playlist.PlaylistManager;
|
import io.github.dsheirer.playlist.PlaylistManager;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexSamplesToNativeBufferModule;
|
||||||
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
|
import io.github.dsheirer.source.Source;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
|
import io.github.dsheirer.spectrum.ComplexDftProcessor;
|
||||||
|
import io.github.dsheirer.spectrum.FrequencyOverlayPanel;
|
||||||
|
import io.github.dsheirer.spectrum.SpectrumPanel;
|
||||||
|
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
||||||
|
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.event.ComponentEvent;
|
||||||
|
import java.awt.event.ComponentListener;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseWheelEvent;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.List;
|
||||||
import jiconfont.icons.font_awesome.FontAwesome;
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
import jiconfont.swing.IconFontSwing;
|
import jiconfont.swing.IconFontSwing;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
@@ -16,9 +56,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JLayeredPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import java.awt.EventQueue;
|
import javax.swing.JSeparator;
|
||||||
import java.text.DecimalFormat;
|
import javax.swing.JSpinner;
|
||||||
|
import javax.swing.SpinnerNumberModel;
|
||||||
|
import javax.swing.event.MouseInputAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display for channel power and squelch details
|
* Display for channel power and squelch details
|
||||||
@@ -27,28 +70,42 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(ChannelPowerPanel.class);
|
private static final Logger mLog = LoggerFactory.getLogger(ChannelPowerPanel.class);
|
||||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
|
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
|
||||||
|
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
private static final String NOT_AVAILABLE = "Not Available";
|
private static final String NOT_AVAILABLE = "Not Available";
|
||||||
private PlaylistManager mPlaylistManager;
|
private PlaylistManager mPlaylistManager;
|
||||||
private ProcessingChain mProcessingChain;
|
private ProcessingChain mProcessingChain;
|
||||||
|
private ComplexSamplesToNativeBufferModule mSampleStreamTapModule = new ComplexSamplesToNativeBufferModule();
|
||||||
|
private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor();
|
||||||
|
private DFTResultsConverter mDFTResultsConverter = new ComplexDecibelConverter();
|
||||||
|
private JLayeredPane mLayeredPanel;
|
||||||
|
private SpectrumPanel mSpectrumPanel;
|
||||||
|
private FrequencyOverlayPanel mFrequencyOverlayPanel;
|
||||||
private DbPowerMeter mPowerMeter = new DbPowerMeter();
|
private DbPowerMeter mPowerMeter = new DbPowerMeter();
|
||||||
private PeakMonitor mPeakMonitor = new PeakMonitor(DbPowerMeter.DEFAULT_MINIMUM_POWER);
|
private PeakMonitor mPeakMonitor = new PeakMonitor(DbPowerMeter.DEFAULT_MINIMUM_POWER);
|
||||||
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
|
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
|
||||||
|
private SpinnerNumberModel mNoiseFloorSpinnerModel;
|
||||||
|
private JSpinner mNoiseFloorSpinner;
|
||||||
private JLabel mPowerLabel;
|
private JLabel mPowerLabel;
|
||||||
private JLabel mPeakLabel;
|
private JLabel mPeakLabel;
|
||||||
private JLabel mSquelchLabel;
|
private JLabel mSquelchLabel;
|
||||||
private JLabel mSquelchValueLabel;
|
private JLabel mSquelchValueLabel;
|
||||||
|
private JLabel mPllFrequencyLabel;
|
||||||
|
private JLabel mPllFrequencyValueLabel;
|
||||||
private JButton mSquelchUpButton;
|
private JButton mSquelchUpButton;
|
||||||
private JButton mSquelchDownButton;
|
private JButton mSquelchDownButton;
|
||||||
|
private JButton mLogIndexesButton;
|
||||||
private double mSquelchThreshold;
|
private double mSquelchThreshold;
|
||||||
|
private boolean mPanelVisible = false;
|
||||||
|
private boolean mDftProcessing = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
*/
|
*/
|
||||||
public ChannelPowerPanel(PlaylistManager playlistManager)
|
public ChannelPowerPanel(PlaylistManager playlistManager, SettingsManager settingsManager)
|
||||||
{
|
{
|
||||||
mPlaylistManager = playlistManager;
|
mPlaylistManager = playlistManager;
|
||||||
|
|
||||||
setLayout(new MigLayout("", "[][grow,fill]", "[grow,fill]"));
|
setLayout(new MigLayout("", "[][][][grow,fill]", "[grow,fill]"));
|
||||||
mPowerMeter.setPeakVisible(true);
|
mPowerMeter.setPeakVisible(true);
|
||||||
mPowerMeter.setSquelchThresholdVisible(true);
|
mPowerMeter.setSquelchThresholdVisible(true);
|
||||||
add(mPowerMeter);
|
add(mPowerMeter);
|
||||||
@@ -85,6 +142,184 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
valuePanel.add(mSquelchDownButton);
|
valuePanel.add(mSquelchDownButton);
|
||||||
|
|
||||||
add(valuePanel);
|
add(valuePanel);
|
||||||
|
add(new JSeparator(JSeparator.VERTICAL));
|
||||||
|
|
||||||
|
JPanel fftPanel = new JPanel();
|
||||||
|
fftPanel.setLayout(new MigLayout("insets 0", "[grow,fill]", "[][grow,fill]"));
|
||||||
|
|
||||||
|
JPanel labelPanel = new JPanel();
|
||||||
|
labelPanel.setLayout(new MigLayout("insets 0", "[grow,fill][right][grow,fill][right][grow,fill][]", ""));
|
||||||
|
labelPanel.add(new JLabel("Channel Spectrum"));
|
||||||
|
|
||||||
|
mPllFrequencyLabel = new JLabel("PLL:");
|
||||||
|
mPllFrequencyLabel.setEnabled(false);
|
||||||
|
labelPanel.add(mPllFrequencyLabel);
|
||||||
|
mPllFrequencyValueLabel = new JLabel("0 Hz");
|
||||||
|
mPllFrequencyValueLabel.setEnabled(false);
|
||||||
|
labelPanel.add(mPllFrequencyValueLabel);
|
||||||
|
|
||||||
|
mNoiseFloorSpinnerModel = new SpinnerNumberModel(18, 8, 36, 1);
|
||||||
|
mNoiseFloorSpinnerModel.addChangeListener(e -> {
|
||||||
|
Number number = mNoiseFloorSpinnerModel.getNumber();
|
||||||
|
mSpectrumPanel.setSampleSize(number.doubleValue());
|
||||||
|
});
|
||||||
|
mNoiseFloorSpinner = new JSpinner(mNoiseFloorSpinnerModel);
|
||||||
|
labelPanel.add(mNoiseFloorSpinner);
|
||||||
|
labelPanel.add(new JLabel("Noise Floor"));
|
||||||
|
|
||||||
|
mLogIndexesButton = new JButton("Log Settings");
|
||||||
|
mLogIndexesButton.addActionListener(e -> {
|
||||||
|
if(mProcessingChain != null)
|
||||||
|
{
|
||||||
|
Source source = mProcessingChain.getSource();
|
||||||
|
|
||||||
|
if(source instanceof PolyphaseChannelSource pcs)
|
||||||
|
{
|
||||||
|
List<Integer> indexes = pcs.getOutputProcessorIndexes();
|
||||||
|
double sampleRate = pcs.getSampleRate();
|
||||||
|
long indexCenterFrequency = pcs.getIndexCenterFrequency();
|
||||||
|
long appliedFrequencyOffset = pcs.getFrequencyOffset();
|
||||||
|
long requestedCenterFrequency = pcs.getFrequency();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Polyphase Channel - BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
|
||||||
|
sb.append(" Center/Requested/Mixer: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
|
||||||
|
sb.append("/").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
|
||||||
|
sb.append("/").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
|
||||||
|
sb.append(" Polyphase Indexes: ").append(indexes);
|
||||||
|
sb.append(" Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
|
||||||
|
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
|
||||||
|
mLog.info(sb.toString());
|
||||||
|
mLog.info("Output Processor: " + pcs.getStateDescription());
|
||||||
|
}
|
||||||
|
else if(source instanceof HalfBandTunerChannelSource<?> hbtcs)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Heterodyne Channel - CF:").append(FREQUENCY_FORMAT.format(hbtcs.getFrequency() / 1E6d));
|
||||||
|
sb.append(" SR:").append(FREQUENCY_FORMAT.format(hbtcs.getSampleRate() / 1E6d));
|
||||||
|
sb.append(" Mixer:").append(FREQUENCY_FORMAT.format(hbtcs.getMixerFrequency() / 1E6d));
|
||||||
|
mLog.info(sb.toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
labelPanel.add(mLogIndexesButton);
|
||||||
|
|
||||||
|
fftPanel.add(labelPanel, "wrap");
|
||||||
|
|
||||||
|
mFrequencyOverlayPanel = new FrequencyOverlayPanel(settingsManager);
|
||||||
|
|
||||||
|
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||||
|
mSpectrumPanel.setSampleSize(18.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layered pane holds the overlapping spectrum and channel panels
|
||||||
|
* and manages the sizing of each panel with the resize listener
|
||||||
|
*/
|
||||||
|
mLayeredPanel = new JLayeredPane();
|
||||||
|
mLayeredPanel.addComponentListener(new ResizeListener());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mouse adapter to handle mouse events over the spectrum
|
||||||
|
* and waterfall panels
|
||||||
|
*/
|
||||||
|
MouseEventProcessor mouser = new MouseEventProcessor();
|
||||||
|
|
||||||
|
mFrequencyOverlayPanel.addMouseListener(mouser);
|
||||||
|
mFrequencyOverlayPanel.addMouseMotionListener(mouser);
|
||||||
|
mFrequencyOverlayPanel.addMouseWheelListener(mouser);
|
||||||
|
|
||||||
|
//Add the spectrum and channel panels to the layered panel
|
||||||
|
mLayeredPanel.add(mSpectrumPanel, 0, 0);
|
||||||
|
mLayeredPanel.add(mFrequencyOverlayPanel, 1, 0);
|
||||||
|
|
||||||
|
fftPanel.add(mLayeredPanel);
|
||||||
|
add(fftPanel);
|
||||||
|
|
||||||
|
mSampleStreamTapModule.setListener(mComplexDftProcessor);
|
||||||
|
mComplexDftProcessor.addConverter(mDFTResultsConverter);
|
||||||
|
mDFTResultsConverter.addListener(mSpectrumPanel);
|
||||||
|
mSpectrumPanel.clearSpectrum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals this panel to indicate if this panel is visible to turn on the FFT processor when the panel is visible
|
||||||
|
* and turn off the FFT processor when it's not.
|
||||||
|
*
|
||||||
|
* Note: this method is intended to be called by the Swing event thread to ensure that only a single thread is
|
||||||
|
* invoking either this method, or the receive() method, since there is no thread synchronization between these
|
||||||
|
* two methods and they each depend on stable access to the mPanelVisible variable.
|
||||||
|
*
|
||||||
|
* @param visible true to indicate that this panel is showing/visible.
|
||||||
|
*/
|
||||||
|
public void setPanelVisible(boolean visible)
|
||||||
|
{
|
||||||
|
mPanelVisible = visible;
|
||||||
|
updateFFTProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates processing state for the DFT processor. Turns on DFT processing when we have a processing chain and
|
||||||
|
* when the user has this tab selected and visible. Otherwise, turns off DFT processing.
|
||||||
|
*/
|
||||||
|
private void updateFFTProcessing()
|
||||||
|
{
|
||||||
|
if(mPanelVisible && mProcessingChain != null)
|
||||||
|
{
|
||||||
|
startDftProcessing();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stopDftProcessing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts DFT processing
|
||||||
|
*/
|
||||||
|
private void startDftProcessing()
|
||||||
|
{
|
||||||
|
if(!mDftProcessing)
|
||||||
|
{
|
||||||
|
mDftProcessing = true;
|
||||||
|
mSampleStreamTapModule.setListener(mComplexDftProcessor);
|
||||||
|
mComplexDftProcessor.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops DFT processing
|
||||||
|
*/
|
||||||
|
private void stopDftProcessing()
|
||||||
|
{
|
||||||
|
if(mDftProcessing)
|
||||||
|
{
|
||||||
|
mSampleStreamTapModule.removeListener();
|
||||||
|
mComplexDftProcessor.stop();
|
||||||
|
mSpectrumPanel.clearSpectrum();
|
||||||
|
mDftProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the decoder's PLL frequency on the swing dispatch thread
|
||||||
|
* @param trackingFrequency that is currently measured.
|
||||||
|
*/
|
||||||
|
private void updatePllFrequency(long trackingFrequency)
|
||||||
|
{
|
||||||
|
//Note: we flip the sign on the error measurement because the value represents the amount of offset the PLL
|
||||||
|
//has to apply to move the signal to center/baseband
|
||||||
|
EventQueue.invokeLater(() -> {
|
||||||
|
String formattedValue = NumberFormat.getInstance().format(-trackingFrequency);
|
||||||
|
mPllFrequencyValueLabel.setText(formattedValue + " Hz");
|
||||||
|
mPllFrequencyValueLabel.setEnabled(true);
|
||||||
|
mPllFrequencyLabel.setEnabled(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
mFrequencyOverlayPanel.setPllTrackingFrequency(trackingFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,6 +359,13 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
|
|
||||||
mPeakLabel.setText("0");
|
mPeakLabel.setText("0");
|
||||||
mPowerLabel.setText("0");
|
mPowerLabel.setText("0");
|
||||||
|
mPllFrequencyLabel.setEnabled(false);
|
||||||
|
mPllFrequencyValueLabel.setText("0 Hz");
|
||||||
|
mPllFrequencyValueLabel.setEnabled(false);
|
||||||
|
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, 0));
|
||||||
|
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(0));
|
||||||
|
mFrequencyOverlayPanel.setPllTrackingFrequency(0);
|
||||||
|
mFrequencyOverlayPanel.setChannelBandwidth(0);
|
||||||
|
|
||||||
mSquelchLabel.setEnabled(false);
|
mSquelchLabel.setEnabled(false);
|
||||||
mSquelchValueLabel.setText("Not Available");
|
mSquelchValueLabel.setText("Not Available");
|
||||||
@@ -138,9 +380,11 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
@Override
|
@Override
|
||||||
public void receive(ProcessingChain processingChain)
|
public void receive(ProcessingChain processingChain)
|
||||||
{
|
{
|
||||||
|
//Disconnect the FFT panel
|
||||||
if(mProcessingChain != null)
|
if(mProcessingChain != null)
|
||||||
{
|
{
|
||||||
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
|
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
|
||||||
|
mProcessingChain.removeModule(mSampleStreamTapModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Invoking reset - we're on the Swing dispatch thread here
|
//Invoking reset - we're on the Swing dispatch thread here
|
||||||
@@ -151,11 +395,35 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
if(mProcessingChain != null)
|
if(mProcessingChain != null)
|
||||||
{
|
{
|
||||||
mProcessingChain.addSourceEventListener(mSourceEventProcessor);
|
mProcessingChain.addSourceEventListener(mSourceEventProcessor);
|
||||||
|
mProcessingChain.addModule(mSampleStreamTapModule);
|
||||||
|
|
||||||
|
Source source = mProcessingChain.getSource();
|
||||||
|
|
||||||
|
if(source instanceof TunerChannelSource tcs)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, tcs.getFrequency()));
|
||||||
|
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(tcs.getSampleRate()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel channel = mPlaylistManager.getChannelProcessingManager().getChannel(mProcessingChain);
|
||||||
|
|
||||||
|
if(channel != null)
|
||||||
|
{
|
||||||
|
TunerChannel tunerChannel = channel.getTunerChannel();
|
||||||
|
|
||||||
|
if(tunerChannel != null)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.setChannelBandwidth(tunerChannel.getBandwidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFFTProcessing();
|
||||||
|
|
||||||
broadcast(SourceEvent.requestCurrentSquelchThreshold(null));
|
broadcast(SourceEvent.requestCurrentSquelchThreshold(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processor for source event stream to capture power level and squelch related source events.
|
* Processor for source event stream to capture power level and squelch related source events.
|
||||||
*/
|
*/
|
||||||
@@ -171,17 +439,12 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
final double power = sourceEvent.getValue().doubleValue();
|
final double power = sourceEvent.getValue().doubleValue();
|
||||||
final double peak = mPeakMonitor.process(power);
|
final double peak = mPeakMonitor.process(power);
|
||||||
|
|
||||||
EventQueue.invokeLater(new Runnable()
|
EventQueue.invokeLater(() -> {
|
||||||
{
|
mPowerMeter.setPower(power);
|
||||||
@Override
|
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
mPowerMeter.setPower(power);
|
|
||||||
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
|
|
||||||
|
|
||||||
mPowerMeter.setPeak(peak);
|
mPowerMeter.setPeak(peak);
|
||||||
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
|
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -205,7 +468,70 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case NOTIFICATION_PLL_FREQUENCY ->
|
||||||
|
{
|
||||||
|
if(sourceEvent.hasValue())
|
||||||
|
{
|
||||||
|
updatePllFrequency(sourceEvent.getValue().longValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors the sizing of the layered pane and resizes the spectrum and
|
||||||
|
* channel panels whenever the layered pane is resized
|
||||||
|
*/
|
||||||
|
public class ResizeListener implements ComponentListener
|
||||||
|
{
|
||||||
|
@Override public void componentResized(ComponentEvent e)
|
||||||
|
{
|
||||||
|
Component c = e.getComponent();
|
||||||
|
|
||||||
|
mSpectrumPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
|
||||||
|
mFrequencyOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void componentHidden(ComponentEvent arg0) {}
|
||||||
|
@Override public void componentMoved(ComponentEvent arg0) {}
|
||||||
|
@Override public void componentShown(ComponentEvent arg0) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mouse event handler for the spectral display panel.
|
||||||
|
*/
|
||||||
|
public class MouseEventProcessor extends MouseInputAdapter
|
||||||
|
{
|
||||||
|
public MouseEventProcessor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void mouseWheelMoved(MouseWheelEvent e) {}
|
||||||
|
@Override public void mouseMoved(MouseEvent event)
|
||||||
|
{
|
||||||
|
update(event);
|
||||||
|
}
|
||||||
|
@Override public void mouseDragged(MouseEvent event) {}
|
||||||
|
@Override public void mousePressed(MouseEvent e) {}
|
||||||
|
@Override public void mouseClicked(MouseEvent event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the cursor display while the mouse is performing actions
|
||||||
|
*/
|
||||||
|
private void update(MouseEvent event)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.setCursorLocation(event.getPoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void mouseEntered(MouseEvent e)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.setCursorVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void mouseExited(MouseEvent e)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.setCursorVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import io.github.dsheirer.preference.UserPreferences;
|
|||||||
import io.github.dsheirer.preference.mp3.MP3Preference;
|
import io.github.dsheirer.preference.mp3.MP3Preference;
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
@@ -99,12 +100,47 @@ public class MP3PreferenceEditor extends HBox
|
|||||||
mMP3SettingComboBox.getItems().addAll(MP3Setting.values());
|
mMP3SettingComboBox.getItems().addAll(MP3Setting.values());
|
||||||
mMP3SettingComboBox.getSelectionModel().select(mMP3Preference.getMP3Setting());
|
mMP3SettingComboBox.getSelectionModel().select(mMP3Preference.getMP3Setting());
|
||||||
mMP3SettingComboBox.getSelectionModel().selectedItemProperty()
|
mMP3SettingComboBox.getSelectionModel().selectedItemProperty()
|
||||||
.addListener((observable, oldValue, newValue) -> mMP3Preference.setMP3Setting(newValue));
|
.addListener((observable, oldValue, newValue) -> {
|
||||||
|
mMP3Preference.setMP3Setting(newValue);
|
||||||
|
updateAudioSampleRateComboBox();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return mMP3SettingComboBox;
|
return mMP3SettingComboBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the input sample rate combo box values and alerts the user whenever we auto-change an input sample rate
|
||||||
|
* due to a change in the MP3 setting value.
|
||||||
|
*/
|
||||||
|
private void updateAudioSampleRateComboBox()
|
||||||
|
{
|
||||||
|
InputAudioFormat currentSelection = getAudioSampleRateComboBox().getSelectionModel().getSelectedItem();
|
||||||
|
MP3Setting setting = getMP3SettingComboBox().getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
|
getAudioSampleRateComboBox().getItems().clear();
|
||||||
|
getAudioSampleRateComboBox().getItems().addAll(setting.getSupportedSampleRates());
|
||||||
|
|
||||||
|
if(currentSelection != null && getAudioSampleRateComboBox().getItems().contains(currentSelection))
|
||||||
|
{
|
||||||
|
getAudioSampleRateComboBox().getSelectionModel().select(currentSelection);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getAudioSampleRateComboBox().getSelectionModel().select(InputAudioFormat.getDefault());
|
||||||
|
|
||||||
|
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
alert.setTitle("Sample Rate Updated");
|
||||||
|
alert.setHeaderText("Input Sample Rate Updated");
|
||||||
|
|
||||||
|
Label wrappingLabel = new Label("Previous input sample rate [" + currentSelection +
|
||||||
|
"] is not supported with encoder setting [" + setting + "]. Sample rate updated to default.");
|
||||||
|
wrappingLabel.setWrapText(true);
|
||||||
|
alert.getDialogPane().setContent(wrappingLabel);
|
||||||
|
alert.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ComboBox<InputAudioFormat> getAudioSampleRateComboBox()
|
private ComboBox<InputAudioFormat> getAudioSampleRateComboBox()
|
||||||
{
|
{
|
||||||
if(mAudioSampleRateComboBox == null)
|
if(mAudioSampleRateComboBox == null)
|
||||||
@@ -113,7 +149,12 @@ public class MP3PreferenceEditor extends HBox
|
|||||||
mAudioSampleRateComboBox.getItems().addAll(InputAudioFormat.values());
|
mAudioSampleRateComboBox.getItems().addAll(InputAudioFormat.values());
|
||||||
mAudioSampleRateComboBox.getSelectionModel().select(mMP3Preference.getAudioSampleRate());
|
mAudioSampleRateComboBox.getSelectionModel().select(mMP3Preference.getAudioSampleRate());
|
||||||
mAudioSampleRateComboBox.getSelectionModel().selectedItemProperty()
|
mAudioSampleRateComboBox.getSelectionModel().selectedItemProperty()
|
||||||
.addListener((observable, oldValue, newValue) -> mMP3Preference.setAudioSampleRate(newValue));
|
.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if(newValue != null)
|
||||||
|
{
|
||||||
|
mMP3Preference.setAudioSampleRate(newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return mAudioSampleRateComboBox;
|
return mAudioSampleRateComboBox;
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* *********************************************************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* 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.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *********************************************************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.icon;
|
package io.github.dsheirer.icon;
|
||||||
|
|
||||||
@@ -22,15 +24,6 @@ import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -43,6 +36,15 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
|
||||||
public class IconModel
|
public class IconModel
|
||||||
{
|
{
|
||||||
@@ -241,7 +243,6 @@ public class IconModel
|
|||||||
if(path != null)
|
if(path != null)
|
||||||
{
|
{
|
||||||
Icon icon = new Icon("", path);
|
Icon icon = new Icon("", path);
|
||||||
|
|
||||||
return getScaledIcon(icon.getIcon(), height);
|
return getScaledIcon(icon.getIcon(), height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -53,6 +53,7 @@ public enum Form
|
|||||||
SITE,
|
SITE,
|
||||||
STATE,
|
STATE,
|
||||||
SYSTEM,
|
SYSTEM,
|
||||||
|
TALKER_ALIAS,
|
||||||
TALKGROUP,
|
TALKGROUP,
|
||||||
TELEPHONE_NUMBER,
|
TELEPHONE_NUMBER,
|
||||||
TONE,
|
TONE,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
package io.github.dsheirer.identifier;
|
package io.github.dsheirer.identifier;
|
||||||
|
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +69,15 @@ public abstract class Identifier<T>
|
|||||||
return mValue;
|
return mValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow subclass implementations to set or update the value after construction.
|
||||||
|
* @param value to update or set.
|
||||||
|
*/
|
||||||
|
protected void setValue(T value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class/type of identifier
|
* Class/type of identifier
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.identifier.alias;
|
||||||
|
|
||||||
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias value provided by the network for the current talker (ie FROM).
|
||||||
|
*/
|
||||||
|
public class DmrTalkerAliasIdentifier extends TalkerAliasIdentifier
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param value of the talker alias
|
||||||
|
*/
|
||||||
|
public DmrTalkerAliasIdentifier(String value)
|
||||||
|
{
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return getValue() != null && !getValue().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Protocol getProtocol()
|
||||||
|
{
|
||||||
|
return Protocol.DMR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DmrTalkerAliasIdentifier create(String value)
|
||||||
|
{
|
||||||
|
return new DmrTalkerAliasIdentifier(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.identifier.alias;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Form;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.string.StringIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias value provided by the network for the current talker (ie FROM).
|
||||||
|
*/
|
||||||
|
public abstract class TalkerAliasIdentifier extends StringIdentifier
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param value of the string/alias identifier
|
||||||
|
*/
|
||||||
|
public TalkerAliasIdentifier(String value)
|
||||||
|
{
|
||||||
|
super(value, IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return getValue() != null && !getValue().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -72,14 +72,15 @@ import io.github.dsheirer.source.SourceEvent;
|
|||||||
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
||||||
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
|
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
|
||||||
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
|
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processing chain provides a framework for connecting a complex or real sample
|
* Processing chain provides a framework for connecting a complex or real sample
|
||||||
@@ -116,12 +117,15 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
|
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
|
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
private List<Module> mModules = new ArrayList<>();
|
|
||||||
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
|
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
|
||||||
private MessageHistory mMessageHistory = new MessageHistory(500);
|
private MessageHistory mMessageHistory = new MessageHistory(500);
|
||||||
private AbstractChannelState mChannelState;
|
private AbstractChannelState mChannelState;
|
||||||
private EventBus mEventBus;
|
private EventBus mEventBus;
|
||||||
protected Source mSource;
|
protected Source mSource;
|
||||||
|
//Lock to protect access to the modules list.
|
||||||
|
private ReentrantLock mModuleLock = new ReentrantLock();
|
||||||
|
private List<Module> mModules = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a processing chain for managing a set of modules
|
* Creates a processing chain for managing a set of modules
|
||||||
@@ -196,30 +200,46 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
*/
|
*/
|
||||||
public void removeTrafficChannelManager()
|
public void removeTrafficChannelManager()
|
||||||
{
|
{
|
||||||
Iterator<Module> it = mModules.iterator();
|
mModuleLock.lock();
|
||||||
|
|
||||||
while(it.hasNext())
|
try
|
||||||
{
|
{
|
||||||
if(it.next() instanceof TrafficChannelManager)
|
Iterator<Module> it = mModules.iterator();
|
||||||
|
|
||||||
|
while(it.hasNext())
|
||||||
{
|
{
|
||||||
it.remove();
|
if(it.next() instanceof TrafficChannelManager)
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
List<Module> modules = new ArrayList<>(mModules);
|
mModuleLock.lock();
|
||||||
|
|
||||||
for(Module module : modules)
|
try
|
||||||
{
|
{
|
||||||
removeModule(module);
|
List<Module> modulesToRemove = new ArrayList<>(mModules);
|
||||||
module.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
mModules.clear();
|
for(Module module : modulesToRemove)
|
||||||
|
{
|
||||||
|
removeModule(module);
|
||||||
|
module.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
mAudioSegmentBroadcaster.dispose();
|
mAudioSegmentBroadcaster.dispose();
|
||||||
mDecodeEventBroadcaster.dispose();
|
mDecodeEventBroadcaster.dispose();
|
||||||
@@ -269,12 +289,21 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
addModule(mSource);
|
addModule(mSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of sample data for this channel.
|
||||||
|
* @return source, may be null.
|
||||||
|
*/
|
||||||
|
public Source getSource()
|
||||||
|
{
|
||||||
|
return mSource;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of current modules for this processing chain
|
* List of current modules for this processing chain
|
||||||
*/
|
*/
|
||||||
public List<Module> getModules()
|
public List<Module> getModules()
|
||||||
{
|
{
|
||||||
return mModules;
|
return Collections.unmodifiableList(mModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,13 +313,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
{
|
{
|
||||||
List<DecoderState> decoderStates = new ArrayList<>();
|
List<DecoderState> decoderStates = new ArrayList<>();
|
||||||
|
|
||||||
for(Module module : mModules)
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(module instanceof DecoderState)
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
decoderStates.add((DecoderState)module);
|
if(module instanceof DecoderState)
|
||||||
|
{
|
||||||
|
decoderStates.add((DecoderState)module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
return decoderStates;
|
return decoderStates;
|
||||||
}
|
}
|
||||||
@@ -322,7 +360,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
*/
|
*/
|
||||||
public void addModule(Module module)
|
public void addModule(Module module)
|
||||||
{
|
{
|
||||||
mModules.add(module);
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mModules.add(module);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
module.setInterModuleEventBus(getEventBus());
|
module.setInterModuleEventBus(getEventBus());
|
||||||
registerListeners(module);
|
registerListeners(module);
|
||||||
registerProviders(module);
|
registerProviders(module);
|
||||||
@@ -337,7 +385,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
unregisterListeners(module);
|
unregisterListeners(module);
|
||||||
unregisterProviders(module);
|
unregisterProviders(module);
|
||||||
module.setInterModuleEventBus(null);
|
module.setInterModuleEventBus(null);
|
||||||
mModules.remove(module);
|
|
||||||
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mModules.remove(module);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -656,18 +714,27 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
+ "sample type - cannot start processing chain");
|
+ "sample type - cannot start processing chain");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start each of the modules */
|
mModuleLock.lock();
|
||||||
for(Module module : mModules)
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
/* Start each of the modules */
|
||||||
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
module.start();
|
try
|
||||||
}
|
{
|
||||||
catch(Exception e)
|
module.start();
|
||||||
{
|
}
|
||||||
mLog.error("Error starting module", e);
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error starting module", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -708,15 +775,28 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Stop each of the remaining modules */
|
/* Stop each of the remaining modules */
|
||||||
for(Module module : mModules)
|
mModuleLock.lock();
|
||||||
{
|
|
||||||
module.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Reset each of the modules
|
try
|
||||||
for(Module module : mModules)
|
|
||||||
{
|
{
|
||||||
module.reset();
|
for(Module module : mModules)
|
||||||
|
{
|
||||||
|
module.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reset each of the modules
|
||||||
|
for(Module module : mModules)
|
||||||
|
{
|
||||||
|
module.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error stopping or resetting modules", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -728,13 +808,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
{
|
{
|
||||||
List<Module> eventLoggingModules = new ArrayList<>();
|
List<Module> eventLoggingModules = new ArrayList<>();
|
||||||
|
|
||||||
for(Module module : mModules)
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(module instanceof EventLogger)
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
eventLoggingModules.add(module);
|
if(module instanceof EventLogger)
|
||||||
|
{
|
||||||
|
eventLoggingModules.add(module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
for(Module eventLoggingModule : eventLoggingModules)
|
for(Module eventLoggingModule : eventLoggingModules)
|
||||||
{
|
{
|
||||||
@@ -749,21 +838,30 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
{
|
{
|
||||||
List<Module> recordingModules = new ArrayList<>();
|
List<Module> recordingModules = new ArrayList<>();
|
||||||
|
|
||||||
for(Module module : mModules)
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(module instanceof ComplexSamplesWaveRecorder)
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
recordingModules.add(module);
|
if(module instanceof ComplexSamplesWaveRecorder)
|
||||||
}
|
{
|
||||||
else if(module instanceof MBECallSequenceRecorder)
|
recordingModules.add(module);
|
||||||
{
|
}
|
||||||
recordingModules.add(module);
|
else if(module instanceof MBECallSequenceRecorder)
|
||||||
}
|
{
|
||||||
else if(module instanceof BinaryRecorder)
|
recordingModules.add(module);
|
||||||
{
|
}
|
||||||
recordingModules.add(module);
|
else if(module instanceof BinaryRecorder)
|
||||||
|
{
|
||||||
|
recordingModules.add(module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
for(Module recordingModule : recordingModules)
|
for(Module recordingModule : recordingModules)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -59,6 +59,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasComplete;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
@@ -66,6 +67,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.Capaci
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
@@ -75,13 +77,12 @@ import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
|||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||||
import io.github.dsheirer.util.PacketUtil;
|
import io.github.dsheirer.util.PacketUtil;
|
||||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoder state for an DMR channel. Maintains the call/data/idle state of the channel and produces events by
|
* Decoder state for an DMR channel. Maintains the call/data/idle state of the channel and produces events by
|
||||||
@@ -207,6 +208,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
processPacket((DMRPacketMessage)message);
|
processPacket((DMRPacketMessage)message);
|
||||||
}
|
}
|
||||||
|
else if(message instanceof UDTShortMessageService sms)
|
||||||
|
{
|
||||||
|
processSMS(sms);
|
||||||
|
}
|
||||||
else if(message instanceof DMRMessage)
|
else if(message instanceof DMRMessage)
|
||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
||||||
@@ -269,6 +274,24 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a short data message carrying SMS text
|
||||||
|
* @param sms
|
||||||
|
*/
|
||||||
|
private void processSMS(UDTShortMessageService sms)
|
||||||
|
{
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||||
|
|
||||||
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(sms.getTimestamp())
|
||||||
|
.eventType(DecodeEventType.SMS)
|
||||||
|
.eventDescription(DecodeEventType.SMS.toString())
|
||||||
|
.details("MESSAGE: " + sms.getSMS())
|
||||||
|
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
||||||
|
.timeslot(sms.getTimeslot())
|
||||||
|
.build();
|
||||||
|
broadcast(smsEvent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a packet message
|
* Processes a packet message
|
||||||
*/
|
*/
|
||||||
@@ -1127,6 +1150,12 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
broadcast(gpsEvent);
|
broadcast(gpsEvent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case FULL_STANDARD_TALKER_ALIAS_COMPLETE:
|
||||||
|
if(message instanceof TalkerAliasComplete tac && tac.hasTalkerAliasIdentifier())
|
||||||
|
{
|
||||||
|
getIdentifierCollection().update(tac.getTalkerAliasIdentifier());
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,56 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.dmr;
|
package io.github.dsheirer.module.decode.dmr;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.IDLEMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Aloha;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.Announcement;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FLCAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FLCAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.SLCAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.SLCAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes DMR messages and performs re-assembly of link control fragments
|
* Processes DMR messages and performs re-assembly of link control fragments
|
||||||
@@ -54,12 +58,13 @@ import java.util.TreeMap;
|
|||||||
public class DMRMessageProcessor implements Listener<IMessage>
|
public class DMRMessageProcessor implements Listener<IMessage>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
||||||
|
private DecodeConfigDMR mConfigDMR;
|
||||||
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
||||||
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
||||||
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
||||||
private PacketSequenceAssembler mPacketSequenceAssembler;
|
private PacketSequenceAssembler mPacketSequenceAssembler;
|
||||||
private SLCAssembler mSLCAssembler = new SLCAssembler();
|
private SLCAssembler mSLCAssembler = new SLCAssembler();
|
||||||
|
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
|
||||||
private Listener<IMessage> mMessageListener;
|
private Listener<IMessage> mMessageListener;
|
||||||
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
||||||
|
|
||||||
@@ -68,6 +73,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
*/
|
*/
|
||||||
public DMRMessageProcessor(DecodeConfigDMR config)
|
public DMRMessageProcessor(DecodeConfigDMR config)
|
||||||
{
|
{
|
||||||
|
mConfigDMR = config;
|
||||||
|
|
||||||
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
|
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
|
||||||
{
|
{
|
||||||
mTimeslotFrequencyMap.put(timeslotFrequency.getNumber(), timeslotFrequency);
|
mTimeslotFrequencyMap.put(timeslotFrequency.getNumber(), timeslotFrequency);
|
||||||
@@ -157,22 +164,43 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Packet Sequence Message Assembly ...
|
//Packet Sequence Message Assembly ...
|
||||||
if(message instanceof Preamble)
|
if(message instanceof Preamble preamble)
|
||||||
{
|
{
|
||||||
mPacketSequenceAssembler.process((Preamble)message);
|
mPacketSequenceAssembler.process(preamble);
|
||||||
}
|
}
|
||||||
else if(message instanceof PacketSequenceHeader)
|
else if(message instanceof PacketSequenceHeader header)
|
||||||
{
|
{
|
||||||
mPacketSequenceAssembler.process((PacketSequenceHeader)message);
|
mPacketSequenceAssembler.process(header);
|
||||||
}
|
}
|
||||||
else if(message instanceof ProprietaryDataHeader)
|
else if(message instanceof ProprietaryDataHeader header)
|
||||||
{
|
{
|
||||||
mPacketSequenceAssembler.process(((ProprietaryDataHeader)message));
|
mPacketSequenceAssembler.process(header);
|
||||||
}
|
}
|
||||||
else if(message instanceof DataBlock)
|
else if(message instanceof DataBlock dataBlock)
|
||||||
{
|
{
|
||||||
mPacketSequenceAssembler.process((DataBlock)message);
|
mPacketSequenceAssembler.process(dataBlock);
|
||||||
}
|
}
|
||||||
|
else if(message instanceof UDTHeader header)
|
||||||
|
{
|
||||||
|
mPacketSequenceAssembler.process(header);
|
||||||
|
}
|
||||||
|
else if((message instanceof IDLEMessage || message instanceof Aloha || message instanceof Announcement) &&
|
||||||
|
message.getTimeslot() != 0)
|
||||||
|
{
|
||||||
|
mPacketSequenceAssembler.dispatchPacketSequence(message.getTimeslot());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reset talker alias assembler on Idle or Terminator
|
||||||
|
if(message.isValid() && (message instanceof IDLEMessage || message instanceof Terminator))
|
||||||
|
{
|
||||||
|
mTalkerAliasAssembler.reset(message.getTimeslot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assemble Talker Alias from FLC message fragments (header & blocks 1-3)
|
||||||
|
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && message.isValid())
|
||||||
|
{
|
||||||
|
dispatch(mTalkerAliasAssembler.process(flc));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Now that the message has been (potentially) enriched, dispatch it to the modules
|
//Now that the message has been (potentially) enriched, dispatch it to the modules
|
||||||
@@ -186,6 +214,26 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
{
|
{
|
||||||
if(mMessageListener != null && message != null)
|
if(mMessageListener != null && message != null)
|
||||||
{
|
{
|
||||||
|
//Configure compressed talkgroups for the DMR talkgroup identifiers in the message.
|
||||||
|
if(mConfigDMR.isUseCompressedTalkgroups())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(Identifier identifier: message.getIdentifiers())
|
||||||
|
{
|
||||||
|
if(identifier instanceof DMRTalkgroup talkgroup)
|
||||||
|
{
|
||||||
|
talkgroup.setCompressed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error applying compressed talkgroup setting to identifier in message: " + message +
|
||||||
|
" [" + message.getClass() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mMessageListener.receive(message);
|
mMessageListener.receive(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer, Zhenyu Mao
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.dmr;
|
package io.github.dsheirer.module.decode.dmr;
|
||||||
|
|
||||||
@@ -25,7 +24,6 @@ import io.github.dsheirer.module.decode.DecoderType;
|
|||||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -40,6 +38,7 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
|||||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||||
private boolean mIgnoreDataCalls = true;
|
private boolean mIgnoreDataCalls = true;
|
||||||
private boolean mIgnoreCRCChecksums = false;
|
private boolean mIgnoreCRCChecksums = false;
|
||||||
|
private boolean mUseCompressedTalkgroups = false;
|
||||||
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
|
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
|
||||||
|
|
||||||
public DecodeConfigDMR()
|
public DecodeConfigDMR()
|
||||||
@@ -109,6 +108,28 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
|||||||
mIgnoreCRCChecksums = ignore;
|
mIgnoreCRCChecksums = ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if decoder should use compressed talkgroups
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "use_compressed_talkgroups")
|
||||||
|
public boolean isUseCompressedTalkgroups()
|
||||||
|
{
|
||||||
|
return mUseCompressedTalkgroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets flag to use compressed talkgroups.
|
||||||
|
*
|
||||||
|
* Converts the air interface value to a compressed value described in ETSI 102 361-2, V.2.4.1, Paragraph C2.1.2
|
||||||
|
* This has been observed in use on Hytera Tier III systems.
|
||||||
|
* See: https://cwh050.blogspot.com/2021/03/converting-flat-number-to-dmr-id.html?m=1
|
||||||
|
* @param useCompressedTalkgroups true to use compressed talkgroups.
|
||||||
|
*/
|
||||||
|
public void setUseCompressedTalkgroups(boolean useCompressedTalkgroups)
|
||||||
|
{
|
||||||
|
mUseCompressedTalkgroups = useCompressedTalkgroups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traffic channel pool size.
|
* Traffic channel pool size.
|
||||||
* @return
|
* @return
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.identifier;
|
package io.github.dsheirer.module.decode.dmr.identifier;
|
||||||
@@ -26,16 +23,33 @@ import io.github.dsheirer.identifier.Role;
|
|||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Talkgroup identifier
|
||||||
|
*/
|
||||||
public class DMRTalkgroup extends TalkgroupIdentifier
|
public class DMRTalkgroup extends TalkgroupIdentifier
|
||||||
{
|
{
|
||||||
public DMRTalkgroup(Integer value)
|
private boolean mCompressed = false;
|
||||||
|
private int mOriginalValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value of the talkgroup
|
||||||
|
*/
|
||||||
|
public DMRTalkgroup(int value)
|
||||||
{
|
{
|
||||||
super(value, Role.TO);
|
super(value, Role.TO);
|
||||||
|
mOriginalValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DMRTalkgroup(Integer value, Role role)
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value of the talkgroup
|
||||||
|
* @param role of the talkgroup
|
||||||
|
*/
|
||||||
|
public DMRTalkgroup(int value, Role role)
|
||||||
{
|
{
|
||||||
super(value, role);
|
super(value, role);
|
||||||
|
mOriginalValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,6 +58,84 @@ public class DMRTalkgroup extends TalkgroupIdentifier
|
|||||||
return Protocol.DMR;
|
return Protocol.DMR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if(mCompressed)
|
||||||
|
{
|
||||||
|
return super.toString() + "(c)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the compressed flag for this value.
|
||||||
|
*
|
||||||
|
* Talkgroup compression is an alternate format for DMR systems that allows the end user to dial a 7-digit number
|
||||||
|
* on their mobile keypad using just the digits 0 - 9, to address a talkgroup. The downside to this is that the
|
||||||
|
* total available talkgroup address space is reduced to just those dialable talkgroups.
|
||||||
|
*
|
||||||
|
* @param compressed true when using compressed talkgroup format.
|
||||||
|
*/
|
||||||
|
public void setCompressed(boolean compressed)
|
||||||
|
{
|
||||||
|
mCompressed = compressed;
|
||||||
|
|
||||||
|
if(mCompressed)
|
||||||
|
{
|
||||||
|
setValue(getCompressed(mOriginalValue));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setValue(mOriginalValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the air interface value to a compressed value described in ETSI 102 361-2, V.2.4.1, Paragraph C2.1.2
|
||||||
|
*
|
||||||
|
* This has been observed in use on Hytera Tier III systems.
|
||||||
|
*
|
||||||
|
* See: https://cwh050.blogspot.com/2021/03/converting-flat-number-to-dmr-id.html?m=1
|
||||||
|
*
|
||||||
|
* @param original value to convert
|
||||||
|
* @return converted value.
|
||||||
|
*/
|
||||||
|
public static int getCompressed(int original)
|
||||||
|
{
|
||||||
|
int converted = 0;
|
||||||
|
int units = original / 1_464_100;
|
||||||
|
converted += (units * 1_000_000);
|
||||||
|
original -= (units * 1_464_100);
|
||||||
|
|
||||||
|
units = original / 146_410;
|
||||||
|
converted += (units * 100_000);
|
||||||
|
original -= (units * 146_410);
|
||||||
|
|
||||||
|
units = original / 14_641;
|
||||||
|
converted += (units * 10_000);
|
||||||
|
original -= (units * 14_641);
|
||||||
|
|
||||||
|
units = original / 1_331;
|
||||||
|
converted += (units * 1_000);
|
||||||
|
original -= (units * 1_331);
|
||||||
|
|
||||||
|
units = original / 121;
|
||||||
|
converted += (units * 100);
|
||||||
|
original -= (units * 121);
|
||||||
|
|
||||||
|
units = original / 11;
|
||||||
|
converted += (units * 10);
|
||||||
|
original -= (units * 11);
|
||||||
|
|
||||||
|
converted += original;
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a DMR TO talkgroup identifier
|
* Creates a DMR TO talkgroup identifier
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,7 +24,12 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAdjacentSiteInformation;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAdjacentSiteInformation;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAloha;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko32;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko44;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko47;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraSmsWaitingNotification;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
||||||
@@ -49,6 +54,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Aloha;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Clear;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Clear;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.MoveTSCC;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.MoveTSCC;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Protect;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.Acknowledge;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.Acknowledge;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.AcknowledgeStatus;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.AcknowledgeStatus;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.RegistrationAccepted;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.RegistrationAccepted;
|
||||||
@@ -78,7 +84,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,6 +205,9 @@ public class CSBKMessageFactory
|
|||||||
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||||
csbk = new PrivateVoiceChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new PrivateVoiceChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
case STANDARD_PROTECT:
|
||||||
|
csbk = new Protect(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
||||||
csbk = new TalkgroupDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new TalkgroupDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
@@ -213,6 +221,9 @@ public class CSBKMessageFactory
|
|||||||
csbk = new Preamble(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new Preamble(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case HYTERA_ALOHA:
|
||||||
|
csbk = new HyteraAloha(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case HYTERA_08_ANNOUNCEMENT:
|
case HYTERA_08_ANNOUNCEMENT:
|
||||||
case HYTERA_68_ANNOUNCEMENT:
|
case HYTERA_68_ANNOUNCEMENT:
|
||||||
AnnouncementType announcementType = HyteraAnnouncement.getAnnouncementType(message);
|
AnnouncementType announcementType = HyteraAnnouncement.getAnnouncementType(message);
|
||||||
@@ -232,7 +243,18 @@ public class CSBKMessageFactory
|
|||||||
case HYTERA_XPT_SITE_STATE:
|
case HYTERA_XPT_SITE_STATE:
|
||||||
csbk = new HyteraXPTSiteState(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new HyteraXPTSiteState(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
case HYTERA_CSBKO_32:
|
||||||
|
csbk = new HyteraCsbko32(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case HYTERA_CSBKO_44:
|
||||||
|
csbk = new HyteraCsbko44(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case HYTERA_CSBKO_47:
|
||||||
|
csbk = new HyteraCsbko47(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case HYTERA_CSBKO_62:
|
||||||
|
csbk = new HyteraSmsWaitingNotification(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case MOTOROLA_CAPMAX_ALOHA:
|
case MOTOROLA_CAPMAX_ALOHA:
|
||||||
csbk = new CapacityMaxAloha(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new CapacityMaxAloha(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
package io.github.dsheirer.module.decode.dmr.message.data.csbk;
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk;
|
||||||
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -50,8 +49,8 @@ public enum Opcode
|
|||||||
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD(Vendor.STANDARD, 35, "ACKNOWLEDGE RESPONSE INBOUND PAYLOAD"),
|
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD(Vendor.STANDARD, 35, "ACKNOWLEDGE RESPONSE INBOUND PAYLOAD"),
|
||||||
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER(Vendor.STANDARD, 36, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER(Vendor.STANDARD, 36, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
||||||
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER(Vendor.STANDARD, 37, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER(Vendor.STANDARD, 37, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
||||||
STANDARD_ANNOUNCEMENT(Vendor.STANDARD, 40, "ANNOUNCEMENT"),
|
|
||||||
STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE(Vendor.STANDARD, 38, "NEGATIVE ACKNOWLEDGE RESPONSE"),
|
STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE(Vendor.STANDARD, 38, "NEGATIVE ACKNOWLEDGE RESPONSE"),
|
||||||
|
STANDARD_ANNOUNCEMENT(Vendor.STANDARD, 40, "ANNOUNCEMENT"),
|
||||||
STANDARD_MAINTENANCE(Vendor.STANDARD, 42, "MAINTENANCE"),
|
STANDARD_MAINTENANCE(Vendor.STANDARD, 42, "MAINTENANCE"),
|
||||||
STANDARD_CLEAR(Vendor.STANDARD, 46, "CLEAR"),
|
STANDARD_CLEAR(Vendor.STANDARD, 46, "CLEAR"),
|
||||||
STANDARD_PROTECT(Vendor.STANDARD, 47, "PROTECT"),
|
STANDARD_PROTECT(Vendor.STANDARD, 47, "PROTECT"),
|
||||||
@@ -90,12 +89,17 @@ public enum Opcode
|
|||||||
MOTOROLA_CAPPLUS_PREAMBLE(Vendor.MOTOROLA_CAPACITY_PLUS, 61, "PREAMBLE"),
|
MOTOROLA_CAPPLUS_PREAMBLE(Vendor.MOTOROLA_CAPACITY_PLUS, 61, "PREAMBLE"),
|
||||||
MOTOROLA_CAPPLUS_SYSTEM_STATUS(Vendor.MOTOROLA_CAPACITY_PLUS, 62, "SYSTEM STATUS"),
|
MOTOROLA_CAPPLUS_SYSTEM_STATUS(Vendor.MOTOROLA_CAPACITY_PLUS, 62, "SYSTEM STATUS"),
|
||||||
|
|
||||||
HYTERA_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA XPT SITE STATE"),
|
HYTERA_CSBKO_32(Vendor.HYTERA_8, 32, "T-III HYTERA 08 CSBKO 32"),
|
||||||
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "T-III 08 ANNOUNCEMENT"),
|
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "T-III 08 ANNOUNCEMENT"),
|
||||||
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "T-III 68 ANNOUNCEMENT"),
|
HYTERA_CSBKO_44(Vendor.HYTERA_8, 44, "T-III HYTERA 08 CSBKO 44"),
|
||||||
|
HYTERA_CSBKO_47(Vendor.HYTERA_8, 47, "T-III HYTERA 08 CSBKO 47"),
|
||||||
|
|
||||||
|
HYTERA_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA XPT SITE STATE"),
|
||||||
|
HYTERA_ALOHA(Vendor.HYTERA_68, 25, "HYTERA ALOHA"),
|
||||||
|
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "T-III 68 ANNOUNCEMENT"),
|
||||||
|
HYTERA_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA XPT PREAMBLE"), //Opcode 54 and/or 55 - See patent on Hytera call forwarding
|
||||||
|
HYTERA_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA CSBKO 62"),
|
||||||
|
|
||||||
//Opcode 54 and/or 55 - See patent on Hytera call forwarding
|
|
||||||
HYTERA_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA XPT PREAMBLE"),
|
|
||||||
|
|
||||||
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.Version;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - Aloha Message
|
||||||
|
*/
|
||||||
|
public class HyteraAloha extends CSBKMessage
|
||||||
|
{
|
||||||
|
//TODO: not sure what bits 16 to 31 are used for ... awaiting documentation.
|
||||||
|
//TODO: bits 32 to 79 seem to align with the standard aloha.
|
||||||
|
|
||||||
|
private static final int[] RESERVED = new int[]{16, 17};
|
||||||
|
private static final int SITE_TIMESLOT_SYNCHRONIZATION = 18;
|
||||||
|
private static final int[] VERSION = new int[]{19, 20, 21};
|
||||||
|
private static final int TIMING_OFFSET = 22;
|
||||||
|
private static final int ACTIVE_NETWORK_CONNECTION_FLAG = 23;
|
||||||
|
private static final int[] MASK = new int[]{24, 25, 26, 27, 28};
|
||||||
|
private static final int[] SERVICE_FUNCTION = new int[]{29, 30};
|
||||||
|
private static final int[] N_RAND_WAIT = new int[]{31, 32, 33, 34};
|
||||||
|
private static final int REGISTRATION_REQUIRED_FLAG = 35;
|
||||||
|
private static final int[] BACKOFF = new int[]{36, 37, 38, 39};
|
||||||
|
private static final int SYSTEM_IDENTITY_CODE_OFFSET = 40;
|
||||||
|
private static final int[] RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||||
|
73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private SystemIdentityCode mSystemIdentityCode;
|
||||||
|
private RadioIdentifier mRadioIdentifier;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public HyteraAloha(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA ALOHA");
|
||||||
|
|
||||||
|
if(hasRadioIdentifier())
|
||||||
|
{
|
||||||
|
sb.append(" TO:").append(getRadioIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" ").append(getSystemIdentityCode().getModel());
|
||||||
|
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
|
||||||
|
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
|
||||||
|
if(hasActiveNetworkConnection())
|
||||||
|
{
|
||||||
|
sb.append(" NET-CONNECTED");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(" NET-DISCONNECTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" SERVICES:").append(getServiceFunction());
|
||||||
|
|
||||||
|
sb.append(" ETSI VER:").append(getVersion());
|
||||||
|
sb.append(" MASK:").append(getMask());
|
||||||
|
|
||||||
|
if(getSystemIdentityCode().getPAR().isMultipleControlChannels())
|
||||||
|
{
|
||||||
|
sb.append(" ").append(getSystemIdentityCode().getPAR());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" ").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Services provided by the control channel.
|
||||||
|
*/
|
||||||
|
public ServiceFunction getServiceFunction()
|
||||||
|
{
|
||||||
|
return ServiceFunction.fromValue(getMessage().getInt(SERVICE_FUNCTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobile subscriber ID masking value. See: 102 361-4 p6.1.3
|
||||||
|
*/
|
||||||
|
public int getMask()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasActiveNetworkConnection()
|
||||||
|
{
|
||||||
|
return getMessage().get(ACTIVE_NETWORK_CONNECTION_FLAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Tier III ETSI 102 361-4 ICD Version Number supported by this system
|
||||||
|
*/
|
||||||
|
public Version getVersion()
|
||||||
|
{
|
||||||
|
return Version.fromValue(getMessage().getInt(VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acknowledged radio identifier
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getRadioIdentifier()
|
||||||
|
{
|
||||||
|
if(mRadioIdentifier == null)
|
||||||
|
{
|
||||||
|
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mRadioIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRadioIdentifier()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(RADIO) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System Identity Code structure
|
||||||
|
*/
|
||||||
|
public SystemIdentityCode getSystemIdentityCode()
|
||||||
|
{
|
||||||
|
if(mSystemIdentityCode == null)
|
||||||
|
{
|
||||||
|
mSystemIdentityCode = new SystemIdentityCode(getMessage(), SYSTEM_IDENTITY_CODE_OFFSET, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSystemIdentityCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
if(hasRadioIdentifier())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getRadioIdentifier());
|
||||||
|
}
|
||||||
|
mIdentifiers.add(getSystemIdentityCode().getNetwork());
|
||||||
|
mIdentifiers.add(getSystemIdentityCode().getSite());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,7 +28,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ public class HyteraAnnouncement extends CSBKMessage
|
|||||||
}
|
}
|
||||||
|
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
sb.append(" HYTERA ").append(getAnnouncementType());
|
sb.append(" HYTERA ANNOUNCEMENT-TYPE:").append(getAnnouncementType());
|
||||||
sb.append(" ").append(getSystemIdentityCode().getModel());
|
sb.append(" ").append(getSystemIdentityCode().getModel());
|
||||||
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
|
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
|
||||||
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
|
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - CSBKO 32 - Possibly Acknowledge Response
|
||||||
|
*
|
||||||
|
* Analysis: ... this was transmitted on the traffic channel while the mobile was at the end of a call, in the terminator
|
||||||
|
* sequence. Since this comes from the SMS gateway 16776906, I wonder if this is a traffic channel message waiting notification?
|
||||||
|
*/
|
||||||
|
public class HyteraCsbko32 extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||||
|
49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||||
|
73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
private RadioIdentifier mDestinationRadio;
|
||||||
|
private RadioIdentifier mSourceRadio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public HyteraCsbko32(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA CSBKO=32 PROBABLE ACKNOWLEDGE RESPONSE");
|
||||||
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
|
sb.append(" TO:").append(getDestinationRadio());
|
||||||
|
sb.append(" UNK1:").append(getUnknown1());
|
||||||
|
sb.append(" UNK2:").append(getUnknown2());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addressed (ie TO) radio identifier
|
||||||
|
* @return radio identifier
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getDestinationRadio()
|
||||||
|
{
|
||||||
|
if(mDestinationRadio == null)
|
||||||
|
{
|
||||||
|
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDestinationRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnknown1()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN_1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnknown2()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN_2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceRadio()
|
||||||
|
{
|
||||||
|
if(mSourceRadio == null)
|
||||||
|
{
|
||||||
|
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getDestinationRadio());
|
||||||
|
mIdentifiers.add(getSourceRadio());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - CSBKO 44 -
|
||||||
|
*
|
||||||
|
* Analysis: ... this was transmitted on the Tier III traffic channel during call termination. This CSBK was
|
||||||
|
* interleaved with every other Terminator message, starting after the first 2x terminator messages. This continued
|
||||||
|
* through the final teardown which was 3x Clear messages.
|
||||||
|
*/
|
||||||
|
public class HyteraCsbko44 extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public HyteraCsbko44(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA UNKNOWN CSBKO=44");
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - CSBKO 47 -
|
||||||
|
*
|
||||||
|
* Analysis: ... this was transmitted on the traffic channel while the mobile was at the end of a call, in the terminator
|
||||||
|
* sequence. Since this comes from the SMS gateway 16776906, I wonder if this is a traffic channel message waiting notification?
|
||||||
|
*/
|
||||||
|
public class HyteraCsbko47 extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||||
|
49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||||
|
73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
private RadioIdentifier mDestinationRadio;
|
||||||
|
private RadioIdentifier mSourceRadio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public HyteraCsbko47(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA CSBKO=47 ######### UNKNOWN");
|
||||||
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
|
sb.append(" TO:").append(getDestinationRadio());
|
||||||
|
sb.append(" UNK1:").append(getUnknown1());
|
||||||
|
sb.append(" UNK2:").append(getUnknown2());
|
||||||
|
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addressed (ie TO) radio identifier
|
||||||
|
* @return radio identifier
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getDestinationRadio()
|
||||||
|
{
|
||||||
|
if(mDestinationRadio == null)
|
||||||
|
{
|
||||||
|
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDestinationRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnknown1()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN_1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnknown2()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN_2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceRadio()
|
||||||
|
{
|
||||||
|
if(mSourceRadio == null)
|
||||||
|
{
|
||||||
|
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getDestinationRadio());
|
||||||
|
mIdentifiers.add(getSourceRadio());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - CSBKO 62 -
|
||||||
|
*
|
||||||
|
* Analysis: this message gets sent on timeslot 2 (not the Tier III control timeslot 1) from the SMS gateway (16776906)
|
||||||
|
* and gets sent to each of the radios involved in the SMS transfer (sender/receiver). I suspect that the network tells
|
||||||
|
* each of the radios to join the control channel and prepare for the SMS message.
|
||||||
|
*/
|
||||||
|
public class HyteraSmsWaitingNotification extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||||
|
49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||||
|
73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
private RadioIdentifier mDestinationRadio;
|
||||||
|
private RadioIdentifier mSourceRadio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public HyteraSmsWaitingNotification(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA PREPARE FOR SMS MESSAGE FOR:").append(getDestinationRadio());
|
||||||
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
|
sb.append(" UNK1:").append(getUnknown1());
|
||||||
|
sb.append(" UNK2:").append(getUnknown2());
|
||||||
|
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addressed (ie TO) radio identifier
|
||||||
|
* @return radio identifier
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getDestinationRadio()
|
||||||
|
{
|
||||||
|
if(mDestinationRadio == null)
|
||||||
|
{
|
||||||
|
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDestinationRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnknown1()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN_1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnknown2()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN_2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceRadio()
|
||||||
|
{
|
||||||
|
if(mSourceRadio == null)
|
||||||
|
{
|
||||||
|
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getDestinationRadio());
|
||||||
|
mIdentifiers.add(getSourceRadio());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -37,7 +37,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -118,6 +117,7 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
|||||||
sb.append(" CLEAR - RETURN TO ").append(getMoveToChannel());
|
sb.append(" CLEAR - RETURN TO ").append(getMoveToChannel());
|
||||||
sb.append(" FM:").append(getSourceRadio());
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getDestinationId());
|
sb.append(" TO:").append(getDestinationId());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,7 +32,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.UnifiedDataTransportFormat;
|
import io.github.dsheirer.module.decode.dmr.message.type.UnifiedDataTransportFormat;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -84,13 +83,13 @@ public class UDTHeader extends DataHeader
|
|||||||
sb.append(" [CRC ERROR]");
|
sb.append(" [CRC ERROR]");
|
||||||
}
|
}
|
||||||
sb.append(" UDT HEADER");
|
sb.append(" UDT HEADER");
|
||||||
if(isSupplementaryData())
|
if(isShortData())
|
||||||
{
|
{
|
||||||
sb.append(" SUPPLEMENTARY DATA");
|
sb.append(" SHORT DATA");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.append(" SHORT DATA");
|
sb.append(" SUPPLEMENTARY DATA");
|
||||||
}
|
}
|
||||||
sb.append(" FM:").append(getSourceLLID());
|
sb.append(" FM:").append(getSourceLLID());
|
||||||
sb.append(" TO:").append(getDestinationLLID());
|
sb.append(" TO:").append(getDestinationLLID());
|
||||||
@@ -106,7 +105,7 @@ public class UDTHeader extends DataHeader
|
|||||||
sb.append(" OPCODE:").append(getOpcode());
|
sb.append(" OPCODE:").append(getOpcode());
|
||||||
}
|
}
|
||||||
sb.append(" PAD NIBBLES:").append(getPadNibbleCount());
|
sb.append(" PAD NIBBLES:").append(getPadNibbleCount());
|
||||||
sb.append(" ").append(getServiceAccessPoint());
|
sb.append(" SAP:").append(getServiceAccessPoint());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +120,15 @@ public class UDTHeader extends DataHeader
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the header contains supplementary data (true) or short data (false)
|
* Indicates if the header is for a short data sequence
|
||||||
|
*/
|
||||||
|
public boolean isShortData()
|
||||||
|
{
|
||||||
|
return !isSupplementaryData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the header is for supplementary data
|
||||||
*/
|
*/
|
||||||
public boolean isSupplementaryData()
|
public boolean isSupplementaryData()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,10 +21,15 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc;
|
|||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.edac.CRCDMR;
|
import io.github.dsheirer.edac.CRCDMR;
|
||||||
|
import io.github.dsheirer.edac.Checksum_5_DMR;
|
||||||
import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR;
|
import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasBlock1;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasBlock2;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasBlock3;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TerminatorData;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TerminatorData;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMessage;
|
||||||
@@ -71,9 +76,22 @@ public class LCMessageFactory
|
|||||||
throw new IllegalArgumentException("Message cannot be null");
|
throw new IllegalArgumentException("Message cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
//RS(12,9,4) can correct up to floor(4/2) = 1 bit error.
|
boolean valid = true;
|
||||||
boolean corrected = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message,
|
|
||||||
isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
|
if(message.size() == 77)
|
||||||
|
{
|
||||||
|
valid = Checksum_5_DMR.isValid(message);
|
||||||
|
}
|
||||||
|
else if(message.size() == 96)
|
||||||
|
{
|
||||||
|
//RS(12,9,4) can correct up to floor(4/2) = 1 bit error.
|
||||||
|
valid = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message,
|
||||||
|
isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.warn("Unrecognized Link Control Message Size: " + message.size());
|
||||||
|
}
|
||||||
|
|
||||||
LCOpcode opcode = FullLCMessage.getOpcode(message);
|
LCOpcode opcode = FullLCMessage.getOpcode(message);
|
||||||
|
|
||||||
@@ -110,15 +128,24 @@ public class LCMessageFactory
|
|||||||
flc = new HyteraTerminator(message, timestamp, timeslot);
|
flc = new HyteraTerminator(message, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
case FULL_STANDARD_TALKER_ALIAS_HEADER:
|
case FULL_STANDARD_TALKER_ALIAS_HEADER:
|
||||||
|
flc = new TalkerAliasHeader(message, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_1:
|
case FULL_STANDARD_TALKER_ALIAS_BLOCK_1:
|
||||||
|
flc = new TalkerAliasBlock1(message, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_2:
|
case FULL_STANDARD_TALKER_ALIAS_BLOCK_2:
|
||||||
|
flc = new TalkerAliasBlock2(message, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case FULL_STANDARD_TALKER_ALIAS_BLOCK_3:
|
case FULL_STANDARD_TALKER_ALIAS_BLOCK_3:
|
||||||
|
flc = new TalkerAliasBlock3(message, timestamp, timeslot);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
flc = new UnknownFullLCMessage(message, timestamp, timeslot);
|
flc = new UnknownFullLCMessage(message, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
flc.setValid(corrected);
|
flc.setValid(valid);
|
||||||
|
|
||||||
return flc;
|
return flc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
package io.github.dsheirer.module.decode.dmr.message.data.lc;
|
package io.github.dsheirer.module.decode.dmr.message.data.lc;
|
||||||
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ public enum LCOpcode
|
|||||||
FULL_STANDARD_TALKER_ALIAS_BLOCK_1(Vendor.STANDARD,true, 5, "TALKER ALIAS BLOCK 1"),
|
FULL_STANDARD_TALKER_ALIAS_BLOCK_1(Vendor.STANDARD,true, 5, "TALKER ALIAS BLOCK 1"),
|
||||||
FULL_STANDARD_TALKER_ALIAS_BLOCK_2(Vendor.STANDARD,true, 6, "TALKER ALIAS BLOCK 2"),
|
FULL_STANDARD_TALKER_ALIAS_BLOCK_2(Vendor.STANDARD,true, 6, "TALKER ALIAS BLOCK 2"),
|
||||||
FULL_STANDARD_TALKER_ALIAS_BLOCK_3(Vendor.STANDARD,true, 7, "TALKER ALIAS BLOCK 3"),
|
FULL_STANDARD_TALKER_ALIAS_BLOCK_3(Vendor.STANDARD,true, 7, "TALKER ALIAS BLOCK 3"),
|
||||||
|
FULL_STANDARD_TALKER_ALIAS_COMPLETE(Vendor.STANDARD, true, -1, "TALKER ALIAS COMPLETE"), //Not part of ICD
|
||||||
FULL_STANDARD_GPS_INFO(Vendor.STANDARD,true, 8, "GPS INFO"),
|
FULL_STANDARD_GPS_INFO(Vendor.STANDARD,true, 8, "GPS INFO"),
|
||||||
FULL_STANDARD_TERMINATOR_DATA(Vendor.STANDARD, true, 48, "TERMINATOR DATA"),
|
FULL_STANDARD_TERMINATOR_DATA(Vendor.STANDARD, true, 48, "TERMINATOR DATA"),
|
||||||
FULL_STANDARD_UNKNOWN(Vendor.STANDARD,true, -1, "FULL UNKNOWN"),
|
FULL_STANDARD_UNKNOWN(Vendor.STANDARD,true, -1, "FULL UNKNOWN"),
|
||||||
@@ -67,6 +68,9 @@ public enum LCOpcode
|
|||||||
|
|
||||||
SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "SHORT UNKNOWN");
|
SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "SHORT UNKNOWN");
|
||||||
|
|
||||||
|
private static final EnumSet<LCOpcode> TALKER_ALIAS_OPCODES = EnumSet.of(FULL_STANDARD_TALKER_ALIAS_HEADER,
|
||||||
|
FULL_STANDARD_TALKER_ALIAS_BLOCK_1, FULL_STANDARD_TALKER_ALIAS_BLOCK_2, FULL_STANDARD_TALKER_ALIAS_BLOCK_3);
|
||||||
|
|
||||||
private Vendor mVendor;
|
private Vendor mVendor;
|
||||||
private boolean mFull;
|
private boolean mFull;
|
||||||
private int mValue;
|
private int mValue;
|
||||||
@@ -111,6 +115,14 @@ public enum LCOpcode
|
|||||||
return !mFull;
|
return !mFull;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this entry is one of the four talker alias opcodes.
|
||||||
|
*/
|
||||||
|
public boolean isTalkerAliasOpcode()
|
||||||
|
{
|
||||||
|
return TALKER_ALIAS_OPCODES.contains(this);
|
||||||
|
}
|
||||||
|
|
||||||
public int getValue()
|
public int getValue()
|
||||||
{
|
{
|
||||||
return mValue;
|
return mValue;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -93,7 +93,7 @@ public class FLCAssembler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a fully-received messsage
|
* Dispatches a fully-received message
|
||||||
*/
|
*/
|
||||||
private FullLCMessage dispatch()
|
private FullLCMessage dispatch()
|
||||||
{
|
{
|
||||||
@@ -133,6 +133,7 @@ public class FLCAssembler
|
|||||||
for(int row = 0; row < 8; row++)
|
for(int row = 0; row < 8; row++)
|
||||||
{
|
{
|
||||||
int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16);
|
int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16);
|
||||||
|
|
||||||
errorCount += rowErrorCount;
|
errorCount += rowErrorCount;
|
||||||
|
|
||||||
if(errorCount > 1)
|
if(errorCount > 1)
|
||||||
@@ -179,6 +180,9 @@ public class FLCAssembler
|
|||||||
{
|
{
|
||||||
extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
|
extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Transfer the checksum parity bit from this row
|
||||||
|
extractedMessage.set(70 + row, descrambled.get(row * 16 + 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
extractedMessage.setCorrectedBitCount(errorCount);
|
extractedMessage.setCorrectedBitCount(errorCount);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -99,4 +99,13 @@ public abstract class FullLCMessage extends LCMessage
|
|||||||
{
|
{
|
||||||
return getMessage().get(ENCRYPTION_FLAG);
|
return getMessage().get(ENCRYPTION_FLAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the reserved bit is set.
|
||||||
|
* @return true if set
|
||||||
|
*/
|
||||||
|
public boolean isReservedBitSet()
|
||||||
|
{
|
||||||
|
return getMessage().get(RESERVED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
|||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -69,6 +68,7 @@ public class GroupVoiceChannelUser extends AbstractVoiceChannelUser
|
|||||||
sb.append(getRadio());
|
sb.append(getRadio());
|
||||||
sb.append(" TO:").append(getTalkgroup());
|
sb.append(" TO:").append(getTalkgroup());
|
||||||
sb.append(" ").append(getServiceOptions());
|
sb.append(" ").append(getServiceOptions());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregates a sequence of talker alias full link control messages to reconstruct the talker alias.
|
||||||
|
*/
|
||||||
|
public class TalkerAliasAssembler
|
||||||
|
{
|
||||||
|
//Timeslot 1 fragments
|
||||||
|
private TalkerAliasHeader mHeaderTs1;
|
||||||
|
private TalkerAliasBlock1 mBlock1Ts1;
|
||||||
|
private TalkerAliasBlock2 mBlock2Ts1;
|
||||||
|
private TalkerAliasBlock3 mBlock3Ts1;
|
||||||
|
|
||||||
|
//Timeslot 2 fragments
|
||||||
|
private TalkerAliasHeader mHeaderTs2;
|
||||||
|
private TalkerAliasBlock1 mBlock1Ts2;
|
||||||
|
private TalkerAliasBlock2 mBlock2Ts2;
|
||||||
|
private TalkerAliasBlock3 mBlock3Ts2;
|
||||||
|
|
||||||
|
//Flags indicating if we have any non-null fragments for the specified timeslot to support resetting.
|
||||||
|
private boolean mEmptyTs1 = true;
|
||||||
|
private boolean mEmptyTs2 = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
*/
|
||||||
|
public TalkerAliasAssembler()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the contents of either timeslot 1 or 2 when a terminator or idle message is received.
|
||||||
|
* @param timeslot 1 or 2
|
||||||
|
*/
|
||||||
|
public void reset(int timeslot)
|
||||||
|
{
|
||||||
|
if(timeslot == 1 && !mEmptyTs1)
|
||||||
|
{
|
||||||
|
mHeaderTs1 = null;
|
||||||
|
mBlock1Ts1 = null;
|
||||||
|
mBlock2Ts1 = null;
|
||||||
|
mBlock3Ts1 = null;
|
||||||
|
mEmptyTs1 = true;
|
||||||
|
}
|
||||||
|
else if(timeslot == 2 && !mEmptyTs2)
|
||||||
|
{
|
||||||
|
mHeaderTs2 = null;
|
||||||
|
mBlock1Ts2 = null;
|
||||||
|
mBlock2Ts2 = null;
|
||||||
|
mBlock3Ts2 = null;
|
||||||
|
mEmptyTs2 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the FLC talker alias header or block and returns a fully assembled alias when all fragments are received.
|
||||||
|
* @param message containing FLC talker alias header or blocks 1-3
|
||||||
|
* @return fully assembled talker alias, if available, or null.
|
||||||
|
*/
|
||||||
|
public TalkerAliasComplete process(FullLCMessage message)
|
||||||
|
{
|
||||||
|
switch(message.getOpcode())
|
||||||
|
{
|
||||||
|
case FULL_STANDARD_TALKER_ALIAS_HEADER:
|
||||||
|
if(message instanceof TalkerAliasHeader tah)
|
||||||
|
{
|
||||||
|
if(message.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mHeaderTs1 = tah;
|
||||||
|
mEmptyTs1 = false;
|
||||||
|
return assemble(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mHeaderTs2 = tah;
|
||||||
|
mEmptyTs2 = false;
|
||||||
|
return assemble(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case FULL_STANDARD_TALKER_ALIAS_BLOCK_1:
|
||||||
|
if(message instanceof TalkerAliasBlock1 tab1)
|
||||||
|
{
|
||||||
|
if(message.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mBlock1Ts1 = tab1;
|
||||||
|
mEmptyTs1 = false;
|
||||||
|
return assemble(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mBlock1Ts2 = tab1;
|
||||||
|
mEmptyTs2 = false;
|
||||||
|
return assemble(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case FULL_STANDARD_TALKER_ALIAS_BLOCK_2:
|
||||||
|
if(message instanceof TalkerAliasBlock2 tab2)
|
||||||
|
{
|
||||||
|
if(message.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mBlock2Ts1 = tab2;
|
||||||
|
mEmptyTs1 = false;
|
||||||
|
return assemble(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mBlock2Ts2 = tab2;
|
||||||
|
mEmptyTs2 = false;
|
||||||
|
return assemble(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case FULL_STANDARD_TALKER_ALIAS_BLOCK_3:
|
||||||
|
if(message instanceof TalkerAliasBlock3 tab3)
|
||||||
|
{
|
||||||
|
if(message.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mBlock3Ts1 = tab3;
|
||||||
|
mEmptyTs1 = false;
|
||||||
|
return assemble(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mBlock3Ts2 = tab3;
|
||||||
|
mEmptyTs2 = false;
|
||||||
|
return assemble(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assembles the complete alias.
|
||||||
|
* @param timeslot to assemble.
|
||||||
|
* @return assembled alias or null.
|
||||||
|
*/
|
||||||
|
private TalkerAliasComplete assemble(int timeslot)
|
||||||
|
{
|
||||||
|
if(timeslot == 1)
|
||||||
|
{
|
||||||
|
return assemble(mHeaderTs1, mBlock1Ts1, mBlock2Ts1, mBlock3Ts1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return assemble(mHeaderTs2, mBlock1Ts2, mBlock2Ts2, mBlock3Ts2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assembles a complete alias from the header and block fragments.
|
||||||
|
* @param header of the alias
|
||||||
|
* @param block1 of the alias, optional
|
||||||
|
* @param block2 of the alias, optional
|
||||||
|
* @param block3 of the alias, optional
|
||||||
|
* @return assembled alias or null.
|
||||||
|
*/
|
||||||
|
private TalkerAliasComplete assemble(TalkerAliasHeader header,
|
||||||
|
TalkerAliasBlock1 block1,
|
||||||
|
TalkerAliasBlock2 block2,
|
||||||
|
TalkerAliasBlock3 block3)
|
||||||
|
{
|
||||||
|
if(header != null)
|
||||||
|
{
|
||||||
|
int bitLength = header.getTotalBitLength();
|
||||||
|
|
||||||
|
if(1 <= bitLength && bitLength <= 49)
|
||||||
|
{
|
||||||
|
return new TalkerAliasComplete(header.getPayloadFragment(), header.getFormat(), header.getCharacterLength(),
|
||||||
|
header.getTimestamp(), header.getTimeslot());
|
||||||
|
}
|
||||||
|
else if(50 <= bitLength && bitLength <= 105 && block1 != null)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage message = new CorrectedBinaryMessage(105);
|
||||||
|
message.load(0, header.getPayloadFragment());
|
||||||
|
message.load(49, block1.getPayloadFragment());
|
||||||
|
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
|
||||||
|
header.getTimestamp(), header.getTimeslot());
|
||||||
|
}
|
||||||
|
else if(106 <= bitLength && bitLength <= 161 && block1 != null && block2 != null)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage message = new CorrectedBinaryMessage(161);
|
||||||
|
message.load(0, header.getPayloadFragment());
|
||||||
|
message.load(49, block1.getPayloadFragment());
|
||||||
|
message.load(105, block2.getPayloadFragment());
|
||||||
|
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
|
||||||
|
header.getTimestamp(), header.getTimeslot());
|
||||||
|
}
|
||||||
|
else if(162 <= bitLength && bitLength <= 217 && block1 != null && block2 != null && block3 != null)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage message = new CorrectedBinaryMessage(217);
|
||||||
|
message.load(0, header.getPayloadFragment());
|
||||||
|
message.load(49, block1.getPayloadFragment());
|
||||||
|
message.load(105, block2.getPayloadFragment());
|
||||||
|
message.load(161, block3.getPayloadFragment());
|
||||||
|
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
|
||||||
|
header.getTimestamp(), header.getTimeslot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias continuation block 1.
|
||||||
|
*/
|
||||||
|
public class TalkerAliasBlock1 extends FullLCMessage
|
||||||
|
{
|
||||||
|
private static final int PAYLOAD_START = 16;
|
||||||
|
private static final int PAYLOAD_END = 72;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param message for link control payload
|
||||||
|
* @param timestamp for the message
|
||||||
|
* @param timeslot where the message was transmitted
|
||||||
|
*/
|
||||||
|
public TalkerAliasBlock1(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload fragment carried by the header.
|
||||||
|
* @return payload fragment.
|
||||||
|
*/
|
||||||
|
public CorrectedBinaryMessage getPayloadFragment()
|
||||||
|
{
|
||||||
|
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("FLC TALKER ALIAS BLOCK 1");
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias continuation block 2.
|
||||||
|
*/
|
||||||
|
public class TalkerAliasBlock2 extends FullLCMessage
|
||||||
|
{
|
||||||
|
private static final int PAYLOAD_START = 16;
|
||||||
|
private static final int PAYLOAD_END = 72;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param message for link control payload
|
||||||
|
* @param timestamp for the message
|
||||||
|
* @param timeslot where the message was transmitted
|
||||||
|
*/
|
||||||
|
public TalkerAliasBlock2(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload fragment carried by the header.
|
||||||
|
* @return payload fragment.
|
||||||
|
*/
|
||||||
|
public CorrectedBinaryMessage getPayloadFragment()
|
||||||
|
{
|
||||||
|
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("FLC TALKER ALIAS BLOCK 2");
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias continuation block 3.
|
||||||
|
*/
|
||||||
|
public class TalkerAliasBlock3 extends FullLCMessage
|
||||||
|
{
|
||||||
|
private static final int PAYLOAD_START = 16;
|
||||||
|
private static final int PAYLOAD_END = 72;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param message for link control payload
|
||||||
|
* @param timestamp for the message
|
||||||
|
* @param timeslot where the message was transmitted
|
||||||
|
*/
|
||||||
|
public TalkerAliasBlock3(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload fragment carried by the header.
|
||||||
|
* @return payload fragment.
|
||||||
|
*/
|
||||||
|
public CorrectedBinaryMessage getPayloadFragment()
|
||||||
|
{
|
||||||
|
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("FLC TALKER ALIAS BLOCK 3");
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.TalkerAliasDataFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete talker alias assembled from talker alias header and 0 or more talker alias blocks.
|
||||||
|
*/
|
||||||
|
public class TalkerAliasComplete extends FullLCMessage
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(TalkerAliasComplete.class);
|
||||||
|
|
||||||
|
private TalkerAliasDataFormat mDataFormat;
|
||||||
|
private int mCharacterCount;
|
||||||
|
private String mAlias;
|
||||||
|
private DmrTalkerAliasIdentifier mTalkerAliasIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param message for link control payload
|
||||||
|
* @param timestamp for the header
|
||||||
|
* @param timeslot for the header and blocks
|
||||||
|
*/
|
||||||
|
public TalkerAliasComplete(CorrectedBinaryMessage message, TalkerAliasDataFormat format, int characterCount,
|
||||||
|
long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
mDataFormat = format;
|
||||||
|
mCharacterCount = characterCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LCOpcode getOpcode()
|
||||||
|
{
|
||||||
|
return LCOpcode.FULL_STANDARD_TALKER_ALIAS_COMPLETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpcodeValue()
|
||||||
|
{
|
||||||
|
return getOpcode().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias value
|
||||||
|
* @return alias value if it can be decoded or empty string.
|
||||||
|
*/
|
||||||
|
public String getAliasValue()
|
||||||
|
{
|
||||||
|
if(mAlias == null)
|
||||||
|
{
|
||||||
|
switch(mDataFormat)
|
||||||
|
{
|
||||||
|
//Note: there are 31 possible 7-bit characters, 27 possible 8-bit characters and 13.5 possible 16-bit
|
||||||
|
//characters. We check each format to ensure character count is at least one and no more than max.
|
||||||
|
case BIT_7:
|
||||||
|
if(1 <= mCharacterCount && mCharacterCount <= 31)
|
||||||
|
{
|
||||||
|
mAlias = getMessage().parseISO7(0, mCharacterCount);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BIT_8:
|
||||||
|
if(1 <= mCharacterCount && mCharacterCount <= 27)
|
||||||
|
{
|
||||||
|
mAlias = getMessage().parseISO8(1, mCharacterCount);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UTF_8:
|
||||||
|
if(1 <= mCharacterCount && mCharacterCount <= 27)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mAlias = getMessage().parseUTF8(1, mCharacterCount);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.info("Error decoding talker alias as UTF-8: " + e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UNICODE_UTF_16_BE:
|
||||||
|
if(1 <= mCharacterCount && mCharacterCount <= 13)
|
||||||
|
{
|
||||||
|
mAlias = getMessage().parseUnicode(1, mCharacterCount);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we couldn't decode, set it to empty so that we don't reprocess every time.
|
||||||
|
if(mAlias == null)
|
||||||
|
{
|
||||||
|
mAlias = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker Alias identifier.
|
||||||
|
* @return identifier or null if the alias string is null or empty, meaning it could not be decoded.
|
||||||
|
*/
|
||||||
|
public DmrTalkerAliasIdentifier getTalkerAliasIdentifier()
|
||||||
|
{
|
||||||
|
if(mTalkerAliasIdentifier == null && getAliasValue() != null && !getAliasValue().isEmpty())
|
||||||
|
{
|
||||||
|
mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(getAliasValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkerAliasIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the talker alias identifier is non-null.
|
||||||
|
* @return true if non-null.
|
||||||
|
*/
|
||||||
|
public boolean hasTalkerAliasIdentifier()
|
||||||
|
{
|
||||||
|
return getTalkerAliasIdentifier() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("FLC TALKER ALIAS:");
|
||||||
|
|
||||||
|
if(hasTalkerAliasIdentifier())
|
||||||
|
{
|
||||||
|
sb.append(getTalkerAliasIdentifier());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append("(empty/error)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(hasTalkerAliasIdentifier())
|
||||||
|
{
|
||||||
|
List<Identifier> identifiers = new ArrayList<>();
|
||||||
|
identifiers.add(getTalkerAliasIdentifier());
|
||||||
|
return identifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.TalkerAliasDataFormat;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias header with format and length description and a fragment of the alias data.
|
||||||
|
*/
|
||||||
|
public class TalkerAliasHeader extends FullLCMessage
|
||||||
|
{
|
||||||
|
private static final int[] FORMAT = new int[]{16, 17};
|
||||||
|
private static final int[] LENGTH = new int[]{18, 19, 20, 21, 22};
|
||||||
|
private static final int PAYLOAD_START = 23;
|
||||||
|
private static final int PAYLOAD_END = 72;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param message for link control payload
|
||||||
|
* @param timestamp for the message
|
||||||
|
* @param timeslot where the message was transmitted
|
||||||
|
*/
|
||||||
|
public TalkerAliasHeader(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatting used for the alias characters.
|
||||||
|
* @return format
|
||||||
|
*/
|
||||||
|
public TalkerAliasDataFormat getFormat()
|
||||||
|
{
|
||||||
|
return TalkerAliasDataFormat.fromValue(getMessage().getInt(FORMAT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the alias in characters.
|
||||||
|
* @return length in characters.
|
||||||
|
*/
|
||||||
|
public int getCharacterLength()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total bit length of the alias characters.
|
||||||
|
*/
|
||||||
|
public int getTotalBitLength()
|
||||||
|
{
|
||||||
|
int total = getFormat().getBitsPerCharacter() * getCharacterLength();
|
||||||
|
|
||||||
|
//Max payload bit length is 217 (49 + 56 + 56 + 56)
|
||||||
|
if(total > 217)
|
||||||
|
{
|
||||||
|
return 217;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload fragment carried by the header.
|
||||||
|
* @return payload fragment.
|
||||||
|
*/
|
||||||
|
public CorrectedBinaryMessage getPayloadFragment()
|
||||||
|
{
|
||||||
|
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("FLC TALKER ALIAS HEADER");
|
||||||
|
sb.append(" FORMAT:").append(getFormat());
|
||||||
|
sb.append(" CHARACTERS:").append(getCharacterLength());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,20 +28,22 @@ import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
|||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Motorola Capacity Plus - Group Voice Channel User
|
* Motorola Capacity Plus - Group Voice Channel User
|
||||||
*/
|
*/
|
||||||
public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser implements ITimeslotFrequencyReceiver
|
public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
|
private static final int[] CAPACITY_PLUS_GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] CONVENTIONAL_GROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
|
||||||
|
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
private static final int[] VOICE_CHANNEL_REPEATER = new int[]{51, 52, 53, 54};
|
private static final int[] VOICE_CHANNEL_REPEATER = new int[]{51, 52, 53, 54};
|
||||||
private static final int[] VOICE_CHANNEL_TIMESLOT = new int[]{55};
|
private static final int[] VOICE_CHANNEL_TIMESLOT = new int[]{55};
|
||||||
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
private static final int[] CAPACITY_PLUS_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
|
private static final int[] CONVENTIONAL_SOURCE_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
|
||||||
|
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
private static final int[] UNKNOWN = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
private static final int[] UNKNOWN = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
private RadioIdentifier mRadio;
|
private RadioIdentifier mRadio;
|
||||||
@@ -69,20 +71,33 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
|||||||
sb.append("[CRC-ERROR] ");
|
sb.append("[CRC-ERROR] ");
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append("FLC MOTOROLA CAP+ GROUP VOICE CHANNEL USER FM:");
|
if(isEncrypted())
|
||||||
sb.append(getRadio());
|
|
||||||
sb.append(" TO:").append(getTalkgroup());
|
|
||||||
sb.append(" ON CHANNEL:");
|
|
||||||
if(hasVoiceChannel())
|
|
||||||
{
|
{
|
||||||
sb.append(getVoiceChannel());
|
sb.append(" ENCRYPTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isReservedBitSet())
|
||||||
|
{
|
||||||
|
sb.append(" RESERVED-BIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getServiceOptions().isCapacityPlus())
|
||||||
|
{
|
||||||
|
sb.append("FLC MOTOROLA CAP+ GROUP VOICE CHANNEL USER");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.append("--");
|
sb.append("FLC MOTOROLA CONV/IP SITE GROUP VOICE CHANNEL USER");
|
||||||
|
}
|
||||||
|
sb.append(" FM:").append(getRadio());
|
||||||
|
sb.append(" TO:").append(getTalkgroup());
|
||||||
|
|
||||||
|
if(hasVoiceChannel())
|
||||||
|
{
|
||||||
|
sb.append(" ON CHANNEL:");
|
||||||
|
sb.append(getVoiceChannel());
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append(" UNK:").append(getUnknown());
|
|
||||||
sb.append(" ").append(getServiceOptions());
|
sb.append(" ").append(getServiceOptions());
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
@@ -119,6 +134,7 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Rest timeslot
|
* Rest timeslot
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public int getVoiceChannelTimeslot()
|
public int getVoiceChannelTimeslot()
|
||||||
@@ -131,7 +147,7 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
|||||||
*/
|
*/
|
||||||
public boolean hasVoiceChannel()
|
public boolean hasVoiceChannel()
|
||||||
{
|
{
|
||||||
return getVoiceChannelRepeater() != 0;
|
return getServiceOptions().isCapacityPlus() && getVoiceChannelRepeater() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,7 +157,14 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
|||||||
{
|
{
|
||||||
if(mRadio == null)
|
if(mRadio == null)
|
||||||
{
|
{
|
||||||
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
|
if(getServiceOptions().isCapacityPlus())
|
||||||
|
{
|
||||||
|
mRadio = DMRRadio.createFrom(getMessage().getInt(CAPACITY_PLUS_SOURCE_ADDRESS));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mRadio = DMRRadio.createFrom(getMessage().getInt(CONVENTIONAL_SOURCE_ADDRESS));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mRadio;
|
return mRadio;
|
||||||
@@ -154,7 +177,14 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
|||||||
{
|
{
|
||||||
if(mTalkgroup == null)
|
if(mTalkgroup == null)
|
||||||
{
|
{
|
||||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
|
if(getServiceOptions().isCapacityPlus())
|
||||||
|
{
|
||||||
|
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CAPACITY_PLUS_GROUP_ADDRESS));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CONVENTIONAL_GROUP_ADDRESS));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mTalkgroup;
|
return mTalkgroup;
|
||||||
@@ -189,12 +219,13 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the LSN to frequency map to the rest channel.
|
* Applies the LSN to frequency map to the rest channel.
|
||||||
|
*
|
||||||
* @param timeslotFrequencies that match the logical timeslots
|
* @param timeslotFrequencies that match the logical timeslots
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||||
{
|
{
|
||||||
if(getVoiceChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
if(getVoiceChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.CapacityPlusServiceOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any Capacity Plus Voice Channel User link control message that contains vendor-specific service options.
|
||||||
|
*/
|
||||||
|
public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage
|
||||||
|
{
|
||||||
|
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
|
||||||
|
private CapacityPlusServiceOptions mServiceOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
*
|
||||||
|
* @param message for the link control payload
|
||||||
|
*/
|
||||||
|
public CapacityPlusVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service options for the call
|
||||||
|
*/
|
||||||
|
public CapacityPlusServiceOptions getServiceOptions()
|
||||||
|
{
|
||||||
|
if(mServiceOptions == null)
|
||||||
|
{
|
||||||
|
mServiceOptions = new CapacityPlusServiceOptions(getMessage().getInt(SERVICE_OPTIONS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mServiceOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,22 +28,20 @@ import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
|||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Motorola Capacity Plus - Wide Area (Multi-Site) Voice Channel User
|
* Motorola Capacity Plus - Wide Area (Multi-Site) Voice Channel User
|
||||||
*/
|
*/
|
||||||
public class CapacityPlusWideAreaVoiceChannelUser extends AbstractVoiceChannelUser implements ITimeslotFrequencyReceiver
|
public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
|
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
private static final int[] REST_REPEATER = new int[]{51, 52, 53, 54};
|
private static final int[] REST_REPEATER = new int[]{51, 52, 53, 54};
|
||||||
private static final int[] REST_TIMESLOT = new int[]{55};
|
private static final int[] REST_TIMESLOT = new int[]{55};
|
||||||
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
private static final int[] UNKNOWN_2 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
private static final int[] UNKNOWN_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
private RadioIdentifier mRadio;
|
private RadioIdentifier mRadio;
|
||||||
private TalkgroupIdentifier mTalkgroup;
|
private TalkgroupIdentifier mTalkgroup;
|
||||||
@@ -70,6 +68,16 @@ public class CapacityPlusWideAreaVoiceChannelUser extends AbstractVoiceChannelUs
|
|||||||
sb.append("[CRC-ERROR] ");
|
sb.append("[CRC-ERROR] ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(isEncrypted())
|
||||||
|
{
|
||||||
|
sb.append(" *ENCRYPTED*");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isReservedBitSet())
|
||||||
|
{
|
||||||
|
sb.append(" *RESERVED-BIT*");
|
||||||
|
}
|
||||||
|
|
||||||
sb.append("FLC MOTOROLA CAP+ WIDE-AREA VOICE CHANNEL USER FM:");
|
sb.append("FLC MOTOROLA CAP+ WIDE-AREA VOICE CHANNEL USER FM:");
|
||||||
sb.append(getRadio());
|
sb.append(getRadio());
|
||||||
sb.append(" TO:").append(getTalkgroup());
|
sb.append(" TO:").append(getTalkgroup());
|
||||||
@@ -102,7 +110,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends AbstractVoiceChannelUs
|
|||||||
*/
|
*/
|
||||||
public int getUnknown2()
|
public int getUnknown2()
|
||||||
{
|
{
|
||||||
return getMessage().getInt(UNKNOWN_2);
|
return getMessage().getInt(UNKNOWN_3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ public class PacketSequence
|
|||||||
{
|
{
|
||||||
private int mTimeslot;
|
private int mTimeslot;
|
||||||
private PacketSequenceHeader mPacketSequenceHeader;
|
private PacketSequenceHeader mPacketSequenceHeader;
|
||||||
|
private UDTHeader mUDTHeader;
|
||||||
private ProprietaryDataHeader mProprietaryDataHeader;
|
private ProprietaryDataHeader mProprietaryDataHeader;
|
||||||
private List<Preamble> mPreambles = new ArrayList<>();
|
private List<Preamble> mPreambles = new ArrayList<>();
|
||||||
private List<DataBlock> mDataBlocks = new ArrayList<>();
|
private List<DataBlock> mDataBlocks = new ArrayList<>();
|
||||||
@@ -51,7 +52,11 @@ public class PacketSequence
|
|||||||
sb.append(" TS:").append(getTimeslot());
|
sb.append(" TS:").append(getTimeslot());
|
||||||
if(mPacketSequenceHeader != null)
|
if(mPacketSequenceHeader != null)
|
||||||
{
|
{
|
||||||
sb.append(mPacketSequenceHeader.toString());
|
sb.append(mPacketSequenceHeader);
|
||||||
|
}
|
||||||
|
else if(mUDTHeader != null)
|
||||||
|
{
|
||||||
|
sb.append(mUDTHeader);
|
||||||
}
|
}
|
||||||
sb.append(" HEADER:").append(hasPacketSequenceHeader());
|
sb.append(" HEADER:").append(hasPacketSequenceHeader());
|
||||||
sb.append(" PROPRIETARY HEADER:").append(hasProprietaryDataHeader());
|
sb.append(" PROPRIETARY HEADER:").append(hasProprietaryDataHeader());
|
||||||
@@ -97,6 +102,15 @@ public class PacketSequence
|
|||||||
mPacketSequenceHeader = header;
|
mPacketSequenceHeader = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unified Data Transport (UDT) header
|
||||||
|
* @param header for a UDT sequence
|
||||||
|
*/
|
||||||
|
public void setUDTHeader(UDTHeader header)
|
||||||
|
{
|
||||||
|
mUDTHeader = header;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proprietary data header
|
* Proprietary data header
|
||||||
*/
|
*/
|
||||||
@@ -138,6 +152,23 @@ public class PacketSequence
|
|||||||
return mPacketSequenceHeader != null;
|
return mPacketSequenceHeader != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UDT header
|
||||||
|
* @return header or null.
|
||||||
|
*/
|
||||||
|
public UDTHeader getUDTHeader()
|
||||||
|
{
|
||||||
|
return mUDTHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this packet sequence has a UDT header
|
||||||
|
*/
|
||||||
|
public boolean hasUDTHeader()
|
||||||
|
{
|
||||||
|
return getUDTHeader() != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Secondary/Proprietary packet sequence header
|
* Secondary/Proprietary packet sequence header
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,7 +71,7 @@ public class PacketSequenceAssembler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the listener to receive packet packet sequence messages
|
* Sets the listener to receive packet sequence messages
|
||||||
* @param listener to receive messages
|
* @param listener to receive messages
|
||||||
*/
|
*/
|
||||||
public void setMessageListener(Listener<IMessage> listener)
|
public void setMessageListener(Listener<IMessage> listener)
|
||||||
@@ -82,11 +83,11 @@ public class PacketSequenceAssembler
|
|||||||
* Dispatches the packet sequence for the specified timeslot and sets the sequence to null.
|
* Dispatches the packet sequence for the specified timeslot and sets the sequence to null.
|
||||||
* @param timeslot to dispatch
|
* @param timeslot to dispatch
|
||||||
*/
|
*/
|
||||||
private void dispatchPacketSequence(int timeslot)
|
public void dispatchPacketSequence(int timeslot)
|
||||||
{
|
{
|
||||||
PacketSequence packetSequence = (timeslot == 1 ? mTimeslot1Sequence : mTimeslot2Sequence);
|
PacketSequence packetSequence = (timeslot == 1 ? mTimeslot1Sequence : mTimeslot2Sequence);
|
||||||
|
|
||||||
if(mMessageListener != null && packetSequence != null && packetSequence.isComplete())
|
if(packetSequence != null && mMessageListener != null)
|
||||||
{
|
{
|
||||||
IMessage message = PacketSequenceMessageFactory.create(packetSequence);
|
IMessage message = PacketSequenceMessageFactory.create(packetSequence);
|
||||||
|
|
||||||
@@ -154,6 +155,27 @@ public class PacketSequenceAssembler
|
|||||||
packetSequence.setPacketSequenceHeader(header);
|
packetSequence.setPacketSequenceHeader(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a UDT header.
|
||||||
|
* @param header
|
||||||
|
*/
|
||||||
|
public void process(UDTHeader header)
|
||||||
|
{
|
||||||
|
int timeslot = header.getTimeslot();
|
||||||
|
|
||||||
|
PacketSequence packetSequence = getPacketSequence(timeslot);
|
||||||
|
|
||||||
|
//If we already have headers or data blocks for the current sequence, then this is a new sequence
|
||||||
|
if(packetSequence.hasPacketSequenceHeader() || packetSequence.hasProprietaryDataHeader() ||
|
||||||
|
packetSequence.hasDataBlocks())
|
||||||
|
{
|
||||||
|
dispatchPacketSequence(timeslot);
|
||||||
|
packetSequence = getPacketSequence(timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
packetSequence.setUDTHeader(header);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a proprietary packet sequence header.
|
* Processes a proprietary packet sequence header.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,6 +26,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
|
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
|
||||||
@@ -35,11 +36,10 @@ import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header;
|
|||||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
||||||
import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket;
|
import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.xcmp.XCMPPacket;
|
import io.github.dsheirer.module.decode.ip.xcmp.XCMPPacket;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a DMR packet sequence into a message
|
* Converts a DMR packet sequence into a message
|
||||||
@@ -55,30 +55,73 @@ public class PacketSequenceMessageFactory
|
|||||||
*/
|
*/
|
||||||
public static IMessage create(PacketSequence packetSequence)
|
public static IMessage create(PacketSequence packetSequence)
|
||||||
{
|
{
|
||||||
if(packetSequence != null && packetSequence.isComplete())
|
if(packetSequence != null)
|
||||||
{
|
{
|
||||||
PacketSequenceHeader primaryHeader = packetSequence.getPacketSequenceHeader();
|
if(packetSequence.hasPacketSequenceHeader())
|
||||||
CorrectedBinaryMessage packet = getPacket(packetSequence, primaryHeader.isConfirmedData());
|
|
||||||
|
|
||||||
if(packet != null)
|
|
||||||
{
|
{
|
||||||
switch(primaryHeader.getServiceAccessPoint())
|
PacketSequenceHeader primaryHeader = packetSequence.getPacketSequenceHeader();
|
||||||
|
CorrectedBinaryMessage packet = getPacket(packetSequence, primaryHeader.isConfirmedData());
|
||||||
|
|
||||||
|
if(packet != null)
|
||||||
{
|
{
|
||||||
case IP_PACKET_DATA:
|
switch(primaryHeader.getServiceAccessPoint())
|
||||||
return createIPPacketData(packetSequence, packet);
|
{
|
||||||
case PROPRIETARY_DATA:
|
case IP_PACKET_DATA:
|
||||||
return createProprietary(packetSequence, packet);
|
return createIPPacketData(packetSequence, packet);
|
||||||
case SHORT_DATA:
|
case PROPRIETARY_DATA:
|
||||||
return createDefinedShortData(packetSequence, packet);
|
return createProprietary(packetSequence, packet);
|
||||||
default:
|
case SHORT_DATA:
|
||||||
mLog.info("Unknown Packet SAP: " + primaryHeader.getServiceAccessPoint() + " - returning unknown packet");
|
return createDefinedShortData(packetSequence, packet);
|
||||||
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
|
default:
|
||||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
mLog.info("Unknown Packet SAP: " + primaryHeader.getServiceAccessPoint() + " - returning unknown packet");
|
||||||
|
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
|
||||||
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(packetSequence.hasUDTHeader() && packetSequence.hasDataBlocks())
|
||||||
|
{
|
||||||
|
UDTHeader header = packetSequence.getUDTHeader();
|
||||||
|
if(header.isShortData())
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage payload = getUnconfirmedPayload(packetSequence.getDataBlocks());
|
||||||
|
return new UDTShortMessageService(header, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: NOTE: Don't create a DMRPacketMessage unless we have a)Packet and b)Packet Sequence Header
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assembles the unconfirmed payload bytes from each of the data blocks into a contiguous binary message.
|
||||||
|
* @param dataBlocks to assemble
|
||||||
|
* @return assembled unconfirmed payload.
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage getUnconfirmedPayload(List<DataBlock> dataBlocks)
|
||||||
|
{
|
||||||
|
if(!dataBlocks.isEmpty())
|
||||||
|
{
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
List<CorrectedBinaryMessage> fragments = new ArrayList<>();
|
||||||
|
for(DataBlock dataBlock: dataBlocks)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage fragment = dataBlock.getUnConfirmedPayload();
|
||||||
|
length += fragment.size();
|
||||||
|
fragments.add(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
CorrectedBinaryMessage combined = new CorrectedBinaryMessage(length);
|
||||||
|
int pointer = 0;
|
||||||
|
for(CorrectedBinaryMessage fragment: fragments)
|
||||||
|
{
|
||||||
|
combined.load(pointer, fragment);
|
||||||
|
pointer += fragment.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.packet;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short Message Service (SMS) using Unified Data Transport
|
||||||
|
*/
|
||||||
|
public class UDTShortMessageService extends DMRMessage
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(UDTShortMessageService.class);
|
||||||
|
private UDTHeader mHeader;
|
||||||
|
private String mSMS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param header for the sequence
|
||||||
|
* @param payload from the data blocks
|
||||||
|
*/
|
||||||
|
public UDTShortMessageService(UDTHeader header, CorrectedBinaryMessage payload)
|
||||||
|
{
|
||||||
|
super(payload, header.getTimestamp(), header.getTimeslot());
|
||||||
|
mHeader = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return mHeader.getIdentifiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CC:").append(mHeader.getSlotType().getColorCode());
|
||||||
|
sb.append(" SMS MESSAGE:").append(getSMS());
|
||||||
|
sb.append(" FROM:").append(mHeader.getSourceLLID());
|
||||||
|
sb.append(" TO:").append(mHeader.getDestinationLLID());
|
||||||
|
sb.append(" HEX:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSMS()
|
||||||
|
{
|
||||||
|
if(mSMS == null)
|
||||||
|
{
|
||||||
|
switch(mHeader.getFormat())
|
||||||
|
{
|
||||||
|
case UNICODE_16:
|
||||||
|
mSMS = parseUnicodePayload();
|
||||||
|
break;
|
||||||
|
case ASCII_7:
|
||||||
|
mSMS = parseISO7Payload();
|
||||||
|
break;
|
||||||
|
case ASCII_8:
|
||||||
|
mSMS = parseISO8Payload();
|
||||||
|
break;
|
||||||
|
case BCD_4_BITS:
|
||||||
|
mSMS = parseBCD4Payload();
|
||||||
|
break;
|
||||||
|
case BINARY:
|
||||||
|
case MOBILE_SUBSCRIBER_OR_TALKGROUP_ADDRESS:
|
||||||
|
case NMEA_GPS_LOCATION_CODED:
|
||||||
|
case IP_ADDRESS:
|
||||||
|
case VENDOR_PROPRIETARY_8:
|
||||||
|
case VENDOR_PROPRIETARY_9:
|
||||||
|
case MIXED_FORMAT:
|
||||||
|
case UNKNOWN:
|
||||||
|
LOGGER.warn("Unrecognized UDT Short Data Format: " + mHeader.getFormat() +
|
||||||
|
" - Please send this to the sdrtrunk developer - Hex:" + getMessage().toHexString() +
|
||||||
|
" As String:" + new String(getMessage().getBytes()));
|
||||||
|
mSMS = "Error:" + new String(getMessage().getBytes());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mSMS == null)
|
||||||
|
{
|
||||||
|
mSMS = "error - unrecognized format: " + mHeader.getFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSMS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a unicode 16-bit payload from the message.
|
||||||
|
* @return parsed message
|
||||||
|
*/
|
||||||
|
private String parseUnicodePayload()
|
||||||
|
{
|
||||||
|
int length = getMessage().size();
|
||||||
|
length -= (mHeader.getPadNibbleCount() * 4);
|
||||||
|
length -= 16; //Exclude the dangling 32-bit CRC
|
||||||
|
length -= 16; //Exclude the dangling 0x0006 ACKNOWLEDGE character
|
||||||
|
|
||||||
|
if(length > 16)
|
||||||
|
{
|
||||||
|
return getMessage().parseUnicode(0, (length / 16));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "(insufficient data)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an ISO-7 payload from the message.
|
||||||
|
* @return parsed message
|
||||||
|
*/
|
||||||
|
private String parseISO7Payload()
|
||||||
|
{
|
||||||
|
int length = getMessage().size();
|
||||||
|
length -= (mHeader.getPadNibbleCount() * 4);
|
||||||
|
|
||||||
|
if(length > 7)
|
||||||
|
{
|
||||||
|
return getMessage().parseISO7(0, (length / 7));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "(insufficient data)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an ISO-8 payload from the message.
|
||||||
|
* @return parsed message
|
||||||
|
*/
|
||||||
|
private String parseISO8Payload()
|
||||||
|
{
|
||||||
|
int length = getMessage().size();
|
||||||
|
length -= (mHeader.getPadNibbleCount() * 4);
|
||||||
|
|
||||||
|
if(length > 8)
|
||||||
|
{
|
||||||
|
return getMessage().parseISO8(0, (length / 8));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "(insufficient data)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a BCD 4-bit payload from the message.
|
||||||
|
* @return parsed message
|
||||||
|
*/
|
||||||
|
private String parseBCD4Payload()
|
||||||
|
{
|
||||||
|
int length = getMessage().size();
|
||||||
|
length -= (mHeader.getPadNibbleCount() * 4);
|
||||||
|
|
||||||
|
if(length > 4)
|
||||||
|
{
|
||||||
|
return getMessage().parseBCD4(0, (length / 4));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "(insufficient data)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -36,7 +36,7 @@ public enum AnnouncementType
|
|||||||
ADJACENT_SITE_INFORMATION(6, "NEIGHBOR SITE INFORMATION"),
|
ADJACENT_SITE_INFORMATION(6, "NEIGHBOR SITE INFORMATION"),
|
||||||
GENERAL_SITE_INFORMATION(7, "SITE INFORMATION"),
|
GENERAL_SITE_INFORMATION(7, "SITE INFORMATION"),
|
||||||
RESERVED_8(8, "RESERVED 8"),
|
RESERVED_8(8, "RESERVED 8"),
|
||||||
RESERVED_9(9, "RESERVED 8"),
|
RESERVED_9(9, "RESERVED 9"),
|
||||||
RESERVED_10(10, "RESERVED 10"),
|
RESERVED_10(10, "RESERVED 10"),
|
||||||
RESERVED_11(11, "RESERVED 11"),
|
RESERVED_11(11, "RESERVED 11"),
|
||||||
RESERVED_12(12, "RESERVED 12"),
|
RESERVED_12(12, "RESERVED 12"),
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.type;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motorola Capacity Plus flavor of service options. Motorola uses the 2x reserved bits from the default implementation.
|
||||||
|
*/
|
||||||
|
public class CapacityPlusServiceOptions extends ServiceOptions
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param value of the service options bitmap
|
||||||
|
*/
|
||||||
|
public CapacityPlusServiceOptions(int value)
|
||||||
|
{
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved bit 1 is used to signal if the system is IP Site Connect or Conventional (true) or Capacity Plus (false).
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isCapacityPlus()
|
||||||
|
{
|
||||||
|
return !isReserved1();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the transmission is interruptible.
|
||||||
|
* @return true if interruptible.
|
||||||
|
*/
|
||||||
|
public boolean isInterruptible()
|
||||||
|
{
|
||||||
|
return isReserved2();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
List<String> flags = new ArrayList<>();
|
||||||
|
|
||||||
|
if(isEmergency())
|
||||||
|
{
|
||||||
|
flags.add("EMERGENCY");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isEncrypted())
|
||||||
|
{
|
||||||
|
flags.add("ENCRYPTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isBroadcastService())
|
||||||
|
{
|
||||||
|
flags.add("BROADCAST");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isOpenVoiceCallMode())
|
||||||
|
{
|
||||||
|
flags.add("OVCM");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isInterruptible())
|
||||||
|
{
|
||||||
|
flags.add("TXI CALL");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getPriority() > 0)
|
||||||
|
{
|
||||||
|
flags.add("PRIORITY-" + getPriority());
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("SERVICE OPTIONS [");
|
||||||
|
if(!flags.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(Joiner.on(",").join(flags));
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
package io.github.dsheirer.module.decode.dmr.message.type;
|
package io.github.dsheirer.module.decode.dmr.message.type;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -31,6 +30,8 @@ public class ServiceOptions
|
|||||||
{
|
{
|
||||||
private static final int EMERGENCY_MASK = 0x80;
|
private static final int EMERGENCY_MASK = 0x80;
|
||||||
private static final int ENCRYPTION_MASK = 0x40;
|
private static final int ENCRYPTION_MASK = 0x40;
|
||||||
|
private static final int RESERVED_1 = 0x20;
|
||||||
|
private static final int RESERVED_2 = 0x10;
|
||||||
private static final int BROADCAST_SERVICE_MASK = 0x8;
|
private static final int BROADCAST_SERVICE_MASK = 0x8;
|
||||||
private static final int OPEN_VOICE_CALL_MODE_MASK = 0x4;
|
private static final int OPEN_VOICE_CALL_MODE_MASK = 0x4;
|
||||||
private static final int PRIORITY_MASK = 0x3;
|
private static final int PRIORITY_MASK = 0x3;
|
||||||
@@ -77,6 +78,24 @@ public class ServiceOptions
|
|||||||
return isSet(OPEN_VOICE_CALL_MODE_MASK);
|
return isSet(OPEN_VOICE_CALL_MODE_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if Reserved 1 bit is set
|
||||||
|
* @return true if set
|
||||||
|
*/
|
||||||
|
public boolean isReserved1()
|
||||||
|
{
|
||||||
|
return isSet(RESERVED_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if Reserved 2 bit is set
|
||||||
|
* @return true if set
|
||||||
|
*/
|
||||||
|
public boolean isReserved2()
|
||||||
|
{
|
||||||
|
return isSet(RESERVED_2);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Priority of the communication
|
* Priority of the communication
|
||||||
* @return priority 0-3, 0 = lowest/routine, 3 = highest
|
* @return priority 0-3, 0 = lowest/routine, 3 = highest
|
||||||
@@ -119,6 +138,16 @@ public class ServiceOptions
|
|||||||
flags.add("OVCM");
|
flags.add("OVCM");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(isReserved1())
|
||||||
|
{
|
||||||
|
flags.add("RSVD1");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isReserved2())
|
||||||
|
{
|
||||||
|
flags.add("RSVD2");
|
||||||
|
}
|
||||||
|
|
||||||
if(getPriority() > 0)
|
if(getPriority() > 0)
|
||||||
{
|
{
|
||||||
flags.add("PRIORITY-" + getPriority());
|
flags.add("PRIORITY-" + getPriority());
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of talker alias data formatting options.
|
||||||
|
*
|
||||||
|
* ETSI 102 361-2 v2.4.1 paragraph 7.2.18
|
||||||
|
*/
|
||||||
|
public enum TalkerAliasDataFormat
|
||||||
|
{
|
||||||
|
BIT_7(0, 7, "ASCII-7"),
|
||||||
|
BIT_8(1, 8, "ASCII-8"),
|
||||||
|
UTF_8(2, 8, "UTF-8"),
|
||||||
|
UNICODE_UTF_16_BE(3, 16, "UNICODE-16");
|
||||||
|
|
||||||
|
private int mValue;
|
||||||
|
private int mBitsPerCharacter;
|
||||||
|
private String mLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value ordinal
|
||||||
|
*/
|
||||||
|
TalkerAliasDataFormat(int value, int bitsPerCharacter, String label)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
mBitsPerCharacter = bitsPerCharacter;
|
||||||
|
mLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ordinal value of the format
|
||||||
|
* @return ordinal value.
|
||||||
|
*/
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bits per character for the format.
|
||||||
|
* @return bits per character
|
||||||
|
*/
|
||||||
|
public int getBitsPerCharacter()
|
||||||
|
{
|
||||||
|
return mBitsPerCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup the format from the ordinal value.
|
||||||
|
* @param value to lookup in range 0-3
|
||||||
|
* @return format for the value.
|
||||||
|
*/
|
||||||
|
public static TalkerAliasDataFormat fromValue(int value)
|
||||||
|
{
|
||||||
|
if(value < 0 || value > 3)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Invalid value - out of range (0-3): " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TalkerAliasDataFormat.values()[value];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ public class VoiceAMessage extends VoiceMessage
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.append(" ").append(getSyncPattern());
|
sb.append("CC:- ").append(getSyncPattern());
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.event;
|
package io.github.dsheirer.module.decode.event;
|
||||||
|
|
||||||
@@ -30,13 +27,13 @@ import io.github.dsheirer.module.decode.event.filter.DecodeEventFilterSet;
|
|||||||
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DecodeEventModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
|
public class DecodeEventModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
|
||||||
{
|
{
|
||||||
@@ -149,10 +146,12 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
|
|||||||
*/
|
*/
|
||||||
public void receive(final IDecodeEvent event)
|
public void receive(final IDecodeEvent event)
|
||||||
{
|
{
|
||||||
if (!mEventFilterSet.passes(event))
|
//Disabled until #1368 decode event is fully implemented
|
||||||
{
|
// if (!mEventFilterSet.passes(event))
|
||||||
return;
|
// {
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
if(!mEvents.contains(event))
|
if(!mEvents.contains(event))
|
||||||
{
|
{
|
||||||
mEvents.add(0, event);
|
mEvents.add(0, event);
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.event;
|
package io.github.dsheirer.module.decode.event;
|
||||||
|
|
||||||
@@ -44,18 +41,24 @@ import io.github.dsheirer.preference.PreferenceType;
|
|||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import java.awt.Color;
|
||||||
import org.slf4j.Logger;
|
import java.awt.Component;
|
||||||
import org.slf4j.LoggerFactory;
|
import java.awt.EventQueue;
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
|
||||||
public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain>
|
public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain>
|
||||||
{
|
{
|
||||||
@@ -94,7 +97,8 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
|||||||
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||||
updateCellRenderers();
|
updateCellRenderers();
|
||||||
|
|
||||||
add(mEventManagementPanel, "span,growx");
|
//Disabled until #1368 decode event is fully implemented
|
||||||
|
// add(mEventManagementPanel, "span,growx");
|
||||||
|
|
||||||
mEmptyScroller = new JScrollPane(mTable);
|
mEmptyScroller = new JScrollPane(mTable);
|
||||||
add(mEmptyScroller);
|
add(mEmptyScroller);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.event;
|
package io.github.dsheirer.module.decode.event;
|
||||||
@@ -50,6 +49,7 @@ public enum DecodeEventType
|
|||||||
EMERGENCY("EMERGENCY"),
|
EMERGENCY("EMERGENCY"),
|
||||||
FUNCTION("Function"),
|
FUNCTION("Function"),
|
||||||
GPS("GPS"),
|
GPS("GPS"),
|
||||||
|
ICMP_PACKET("ICMP Packet"),
|
||||||
ID_ANI("ANI"),
|
ID_ANI("ANI"),
|
||||||
ID_UNIQUE("Unique ID"),
|
ID_UNIQUE("Unique ID"),
|
||||||
IP_PACKET("IP Packet"),
|
IP_PACKET("IP Packet"),
|
||||||
@@ -63,9 +63,11 @@ public enum DecodeEventType
|
|||||||
RESPONSE("Response"),
|
RESPONSE("Response"),
|
||||||
RESPONSE_PACKET("Response Packet"),
|
RESPONSE_PACKET("Response Packet"),
|
||||||
SDM("Short Data Message"),
|
SDM("Short Data Message"),
|
||||||
|
SMS("SMS"),
|
||||||
STATION_ID("Station ID"),
|
STATION_ID("Station ID"),
|
||||||
STATUS("Status"),
|
STATUS("Status"),
|
||||||
UDP_PACKET("UDP/IP Packet"),
|
UDP_PACKET("UDP/IP Packet"),
|
||||||
|
UNKNOWN_PACKET("Unknown Packet"),
|
||||||
UNKNOWN("Unknown");
|
UNKNOWN("Unknown");
|
||||||
|
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.ip.ars;
|
package io.github.dsheirer.module.decode.ip.ars;
|
||||||
@@ -25,11 +24,10 @@ import io.github.dsheirer.identifier.Identifier;
|
|||||||
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSDevice;
|
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSDevice;
|
||||||
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSPassword;
|
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSPassword;
|
||||||
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSUser;
|
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSUser;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatic Registration Service - Device Registration
|
* Automatic Registration Service - Device Registration
|
||||||
@@ -176,7 +174,7 @@ public class DeviceRegistration extends ARSHeader
|
|||||||
|
|
||||||
pointer += 8;
|
pointer += 8;
|
||||||
|
|
||||||
if(deviceIdentifierSize > 0)
|
if(deviceIdentifierSize > 0 && deviceIdentifierSize < 8)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
@@ -186,21 +184,13 @@ public class DeviceRegistration extends ARSHeader
|
|||||||
pointer += 8;
|
pointer += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
mDevice = ARSDevice.createFrom(sb.toString());
|
||||||
{
|
|
||||||
int device = Integer.parseInt(sb.toString());
|
|
||||||
mDevice = ARSDevice.createFrom(device);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
mLog.error("Error parsing ARS device identifier from value [" + sb.toString() + "]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int userIdentifierSize = getMessage().getInt(BYTE_VALUE, pointer);
|
int userIdentifierSize = getMessage().getInt(BYTE_VALUE, pointer);
|
||||||
pointer += 8;
|
pointer += 8;
|
||||||
|
|
||||||
if(userIdentifierSize > 0)
|
if(userIdentifierSize > 0 && userIdentifierSize < 8)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
@@ -216,7 +206,7 @@ public class DeviceRegistration extends ARSHeader
|
|||||||
int passwordSize = getMessage().getInt(BYTE_VALUE, pointer);
|
int passwordSize = getMessage().getInt(BYTE_VALUE, pointer);
|
||||||
pointer += 8;
|
pointer += 8;
|
||||||
|
|
||||||
if(passwordSize > 0)
|
if(passwordSize > 0 && passwordSize < 8)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.ip.ars;
|
package io.github.dsheirer.module.decode.ip.ars;
|
||||||
@@ -25,11 +24,10 @@ import io.github.dsheirer.identifier.Identifier;
|
|||||||
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSDevice;
|
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSDevice;
|
||||||
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSPassword;
|
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSPassword;
|
||||||
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSUser;
|
import io.github.dsheirer.module.decode.ip.ars.identifier.ARSUser;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class UserRegistration extends ARSHeader
|
public class UserRegistration extends ARSHeader
|
||||||
{
|
{
|
||||||
@@ -108,7 +106,7 @@ public class UserRegistration extends ARSHeader
|
|||||||
|
|
||||||
pointer += 8;
|
pointer += 8;
|
||||||
|
|
||||||
if(deviceIdentifierSize > 0)
|
if(deviceIdentifierSize > 0 && deviceIdentifierSize < 8)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
@@ -118,22 +116,13 @@ public class UserRegistration extends ARSHeader
|
|||||||
pointer += 8;
|
pointer += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
mDevice = ARSDevice.createFrom(sb.toString());
|
||||||
{
|
|
||||||
int device = Integer.parseInt(sb.toString());
|
|
||||||
mDevice = ARSDevice.createFrom(device);
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
//Abort processing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int userIdentifierSize = getMessage().getInt(BYTE_VALUE, pointer);
|
int userIdentifierSize = getMessage().getInt(BYTE_VALUE, pointer);
|
||||||
pointer += 8;
|
pointer += 8;
|
||||||
|
|
||||||
if(userIdentifierSize > 0)
|
if(userIdentifierSize > 0 && deviceIdentifierSize < 8)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
@@ -149,7 +138,7 @@ public class UserRegistration extends ARSHeader
|
|||||||
int passwordSize = getMessage().getInt(BYTE_VALUE, pointer);
|
int passwordSize = getMessage().getInt(BYTE_VALUE, pointer);
|
||||||
pointer += 8;
|
pointer += 8;
|
||||||
|
|
||||||
if(passwordSize > 0)
|
if(passwordSize > 0 && passwordSize < 8)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,20 +22,20 @@ package io.github.dsheirer.module.decode.ip.ars.identifier;
|
|||||||
import io.github.dsheirer.identifier.Form;
|
import io.github.dsheirer.identifier.Form;
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
import io.github.dsheirer.identifier.string.StringIdentifier;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatic Registration Service - Device Identifier
|
* Automatic Registration Service - Device Identifier
|
||||||
*/
|
*/
|
||||||
public class ARSDevice extends IntegerIdentifier
|
public class ARSDevice extends StringIdentifier
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
* @param value of the device identifier
|
* @param value of the device identifier
|
||||||
* @param role of the device (TO/FROM)
|
* @param role of the device (TO/FROM)
|
||||||
*/
|
*/
|
||||||
public ARSDevice(int value, Role role)
|
public ARSDevice(String value, Role role)
|
||||||
{
|
{
|
||||||
super(value, IdentifierClass.USER, Form.ARS_DEVICE, role);
|
super(value, IdentifierClass.USER, Form.ARS_DEVICE, role);
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ public class ARSDevice extends IntegerIdentifier
|
|||||||
/**
|
/**
|
||||||
* Creates an ARS device with a FROM role
|
* Creates an ARS device with a FROM role
|
||||||
*/
|
*/
|
||||||
public static ARSDevice createFrom(int value)
|
public static ARSDevice createFrom(String value)
|
||||||
{
|
{
|
||||||
return new ARSDevice(value, Role.FROM);
|
return new ARSDevice(value, Role.FROM);
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ public class ARSDevice extends IntegerIdentifier
|
|||||||
/**
|
/**
|
||||||
* Creates an ARS device with a TO role
|
* Creates an ARS device with a TO role
|
||||||
*/
|
*/
|
||||||
public static ARSDevice createTo(int value)
|
public static ARSDevice createTo(String value)
|
||||||
{
|
{
|
||||||
return new ARSDevice(value, Role.TO);
|
return new ARSDevice(value, Role.TO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -43,8 +43,10 @@ import io.github.dsheirer.module.decode.event.DecodeEvent;
|
|||||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.ip.IPacket;
|
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
|
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.cellocator.MCGPPacket;
|
import io.github.dsheirer.module.decode.ip.cellocator.MCGPPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.icmp.ICMPPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
||||||
import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket;
|
import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
||||||
@@ -87,7 +89,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUn
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrant;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrant;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrantUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrantUpdate;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPMessage;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketMessage;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.isp.UMBTCTelephoneInterconnectRequestExplicitDialing;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.isp.UMBTCTelephoneInterconnectRequestExplicitDialing;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
|
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
|
||||||
@@ -139,12 +140,11 @@ import io.github.dsheirer.module.decode.p25.reference.ServiceOptions;
|
|||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.PacketUtil;
|
import io.github.dsheirer.util.PacketUtil;
|
||||||
|
import java.util.List;
|
||||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoder state for an APCO25 channel. Maintains the call/data/idle state of the channel and produces events by
|
* Decoder state for an APCO25 channel. Maintains the call/data/idle state of the channel and produces events by
|
||||||
* monitoring the decoded message stream.
|
* monitoring the decoded message stream.
|
||||||
@@ -877,12 +877,12 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
|
|
||||||
if(message instanceof SNDCPPacketMessage)
|
if(message instanceof SNDCPPacketMessage)
|
||||||
{
|
{
|
||||||
SNDCPPacketMessage sndcp = (SNDCPPacketMessage)message;
|
SNDCPPacketMessage sndcp = (SNDCPPacketMessage) message;
|
||||||
getIdentifierCollection().update(sndcp.getIdentifiers());
|
getIdentifierCollection().update(sndcp.getIdentifiers());
|
||||||
}
|
}
|
||||||
else if(message instanceof PacketMessage)
|
else if(message instanceof PacketMessage)
|
||||||
{
|
{
|
||||||
PacketMessage packetMessage = (PacketMessage)message;
|
PacketMessage packetMessage = (PacketMessage) message;
|
||||||
getIdentifierCollection().remove(IdentifierClass.USER);
|
getIdentifierCollection().remove(IdentifierClass.USER);
|
||||||
getIdentifierCollection().update(packetMessage.getIdentifiers());
|
getIdentifierCollection().update(packetMessage.getIdentifiers());
|
||||||
|
|
||||||
@@ -890,41 +890,41 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
|
|
||||||
if(packet instanceof IPV4Packet)
|
if(packet instanceof IPV4Packet)
|
||||||
{
|
{
|
||||||
IPV4Packet ipv4 = (IPV4Packet)packet;
|
IPV4Packet ipv4 = (IPV4Packet) packet;
|
||||||
|
|
||||||
IPacket ipPayload = ipv4.getPayload();
|
IPacket ipPayload = ipv4.getPayload();
|
||||||
|
|
||||||
if(ipPayload instanceof UDPPacket)
|
if(ipPayload instanceof UDPPacket)
|
||||||
{
|
{
|
||||||
UDPPacket udpPacket = (UDPPacket)ipPayload;
|
UDPPacket udpPacket = (UDPPacket) ipPayload;
|
||||||
|
|
||||||
IPacket udpPayload = udpPacket.getPayload();
|
IPacket udpPayload = udpPacket.getPayload();
|
||||||
|
|
||||||
if(udpPayload instanceof ARSPacket)
|
if(udpPayload instanceof ARSPacket)
|
||||||
{
|
{
|
||||||
ARSPacket arsPacket = (ARSPacket)udpPayload;
|
ARSPacket arsPacket = (ARSPacket) udpPayload;
|
||||||
|
|
||||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
for(Identifier identifier: packet.getIdentifiers())
|
for(Identifier identifier : packet.getIdentifiers())
|
||||||
{
|
{
|
||||||
ic.update(identifier);
|
ic.update(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
.eventDescription(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE.toString())
|
.eventDescription(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE.toString())
|
||||||
.identifiers(ic)
|
.identifiers(ic)
|
||||||
.details(arsPacket.toString() + " " + ipv4.toString())
|
.details(arsPacket.toString() + " " + ipv4.toString())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
broadcast(packetEvent);
|
broadcast(packetEvent);
|
||||||
}
|
}
|
||||||
else if(udpPayload instanceof MCGPPacket)
|
else if(udpPayload instanceof MCGPPacket)
|
||||||
{
|
{
|
||||||
MCGPPacket mcgpPacket = (MCGPPacket)udpPayload;
|
MCGPPacket mcgpPacket = (MCGPPacket) udpPayload;
|
||||||
|
|
||||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
for(Identifier identifier: packet.getIdentifiers())
|
for(Identifier identifier : packet.getIdentifiers())
|
||||||
{
|
{
|
||||||
ic.update(identifier);
|
ic.update(identifier);
|
||||||
}
|
}
|
||||||
@@ -954,7 +954,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
|
|
||||||
GeoPosition geoPosition = PacketUtil.extractGeoPosition(lrrpPacket);
|
GeoPosition geoPosition = PacketUtil.extractGeoPosition(lrrpPacket);
|
||||||
|
|
||||||
if (geoPosition != null)
|
if(geoPosition != null)
|
||||||
{
|
{
|
||||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(message.getTimestamp())
|
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(message.getTimestamp())
|
||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
@@ -970,21 +970,72 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
for(Identifier identifier: packet.getIdentifiers())
|
for(Identifier identifier : packet.getIdentifiers())
|
||||||
{
|
{
|
||||||
ic.update(identifier);
|
ic.update(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
.eventDescription(DecodeEventType.UDP_PACKET.toString())
|
.eventDescription(DecodeEventType.UDP_PACKET.toString())
|
||||||
.identifiers(ic)
|
.identifiers(ic)
|
||||||
.details(ipv4.toString())
|
.details(ipv4.toString())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
broadcast(packetEvent);
|
broadcast(packetEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(ipPayload instanceof ICMPPacket)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
for(Identifier identifier : packet.getIdentifiers())
|
||||||
|
{
|
||||||
|
ic.update(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
|
.eventDescription(DecodeEventType.ICMP_PACKET.toString())
|
||||||
|
.identifiers(ic)
|
||||||
|
.details(ipv4.toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
broadcast(packetEvent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
for(Identifier identifier : packet.getIdentifiers())
|
||||||
|
{
|
||||||
|
ic.update(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
|
.eventDescription(DecodeEventType.IP_PACKET.toString())
|
||||||
|
.identifiers(ic)
|
||||||
|
.details(ipv4.toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
broadcast(packetEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(packet instanceof UnknownPacket)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
for(Identifier identifier : packet.getIdentifiers())
|
||||||
|
{
|
||||||
|
ic.update(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
|
.eventDescription(DecodeEventType.UNKNOWN_PACKET.toString())
|
||||||
|
.identifiers(ic)
|
||||||
|
.details(packet.toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
broadcast(packetEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -992,18 +1043,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
/**
|
/**
|
||||||
* Sub-Network Dependent Convergence Protocol (SNDCP)
|
* Sub-Network Dependent Convergence Protocol (SNDCP)
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message to process
|
||||||
*/
|
*/
|
||||||
private void processSNDCP(P25Message message)
|
private void processSNDCP(P25Message message)
|
||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA));
|
broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA));
|
||||||
|
|
||||||
if(message.isValid() && message instanceof SNDCPPacketMessage)
|
if(message.isValid() && message instanceof SNDCPPacketMessage sndcpPacket)
|
||||||
{
|
{
|
||||||
SNDCPPacketMessage sndcpPacket = (SNDCPPacketMessage)message;
|
|
||||||
|
|
||||||
SNDCPMessage sndcpMessage = sndcpPacket.getSNDCPMessage();
|
|
||||||
|
|
||||||
MutableIdentifierCollection ic = getMutableIdentifierCollection(sndcpPacket.getIdentifiers());
|
MutableIdentifierCollection ic = getMutableIdentifierCollection(sndcpPacket.getIdentifiers());
|
||||||
|
|
||||||
switch(sndcpPacket.getSNDCPPacketHeader().getPDUType())
|
switch(sndcpPacket.getSNDCPPacketHeader().getPDUType())
|
||||||
|
|||||||
@@ -116,6 +116,24 @@ public class MP3Preference extends Preference
|
|||||||
{
|
{
|
||||||
mInputAudioFormat = InputAudioFormat.getDefault();
|
mInputAudioFormat = InputAudioFormat.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Ensure the input sample rate is supported by the MP3 setting, or change it to the first supported rate.
|
||||||
|
MP3Setting setting = getMP3Setting();
|
||||||
|
|
||||||
|
if(!setting.getSupportedSampleRates().contains(mInputAudioFormat))
|
||||||
|
{
|
||||||
|
InputAudioFormat supportedAudioFormat = InputAudioFormat.getDefault();
|
||||||
|
|
||||||
|
//Default should always be supported by all mp3 settings, but fall-back to a supported rate if not.
|
||||||
|
if(!setting.getSupportedSampleRates().contains(supportedAudioFormat))
|
||||||
|
{
|
||||||
|
supportedAudioFormat = setting.getSupportedSampleRates().get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.warn("MP3 Setting [" + setting + "] does not support input sample rate [" + getAudioSampleRate().name() +
|
||||||
|
"] - updating to supported sample rate [" + supportedAudioFormat.name() + "]");
|
||||||
|
setAudioSampleRate(supportedAudioFormat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mInputAudioFormat;
|
return mInputAudioFormat;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,9 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.record.binary;
|
package io.github.dsheirer.record.binary;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -29,6 +26,8 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class BinaryReader implements Iterator<ByteBuffer>, AutoCloseable
|
public class BinaryReader implements Iterator<ByteBuffer>, AutoCloseable
|
||||||
{
|
{
|
||||||
@@ -94,7 +93,7 @@ public class BinaryReader implements Iterator<ByteBuffer>, AutoCloseable
|
|||||||
byte[] readBytes = new byte[mBufferSize];
|
byte[] readBytes = new byte[mBufferSize];
|
||||||
int bytesRead = mInputStream.read(readBytes);
|
int bytesRead = mInputStream.read(readBytes);
|
||||||
|
|
||||||
if(bytesRead < readBytes.length)
|
if(bytesRead < readBytes.length && bytesRead > 0)
|
||||||
{
|
{
|
||||||
readBytes = Arrays.copyOf(readBytes, bytesRead);
|
readBytes = Arrays.copyOf(readBytes, bytesRead);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.sample.complex;
|
||||||
|
|
||||||
|
import io.github.dsheirer.buffer.INativeBuffer;
|
||||||
|
import io.github.dsheirer.module.Module;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple module that can be added live to a processing chain to tap into the stream of ComplexSamples buffers and
|
||||||
|
* convert them to INativeBuffers for relay to a registered native buffer listener.
|
||||||
|
*/
|
||||||
|
public class ComplexSamplesToNativeBufferModule extends Module implements IComplexSamplesListener, Listener<ComplexSamples>
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(ComplexSamplesToNativeBufferModule.class);
|
||||||
|
private Listener<INativeBuffer> mNativeBufferListener;
|
||||||
|
|
||||||
|
public ComplexSamplesToNativeBufferModule()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Listener<ComplexSamples> getComplexSamplesListener()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary receive method to process complex samples buffer by wrapping with a native buffer adapter and sending
|
||||||
|
* to the registered listener.
|
||||||
|
*
|
||||||
|
* Note: this method is synchronized to ensure that multiple calling threads don't step on each other.
|
||||||
|
*
|
||||||
|
* @param complexSamples to process and relay.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void receive(ComplexSamples complexSamples)
|
||||||
|
{
|
||||||
|
if(mNativeBufferListener != null)
|
||||||
|
{
|
||||||
|
mNativeBufferListener.receive(new ComplexSamplesNativeBufferAdapter(complexSamples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive native buffers.
|
||||||
|
* @param listener to register
|
||||||
|
*/
|
||||||
|
public void setListener(Listener<INativeBuffer> listener)
|
||||||
|
{
|
||||||
|
mNativeBufferListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a listener from receiving native buffers.
|
||||||
|
*/
|
||||||
|
public void removeListener()
|
||||||
|
{
|
||||||
|
mNativeBufferListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,6 +29,7 @@ public class SourceEvent
|
|||||||
public enum Event
|
public enum Event
|
||||||
{
|
{
|
||||||
NOTIFICATION_CHANNEL_COUNT_CHANGE,
|
NOTIFICATION_CHANNEL_COUNT_CHANGE,
|
||||||
|
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION,
|
||||||
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
|
NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
|
||||||
NOTIFICATION_CHANNEL_POWER,
|
NOTIFICATION_CHANNEL_POWER,
|
||||||
NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE,
|
NOTIFICATION_CHANNEL_SAMPLE_RATE_CHANGE,
|
||||||
@@ -40,6 +41,7 @@ public class SourceEvent
|
|||||||
NOTIFICATION_FREQUENCY_ROTATION_FAILURE,
|
NOTIFICATION_FREQUENCY_ROTATION_FAILURE,
|
||||||
NOTIFICATION_MEASURED_FREQUENCY_ERROR,
|
NOTIFICATION_MEASURED_FREQUENCY_ERROR,
|
||||||
NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED,
|
NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED,
|
||||||
|
NOTIFICATION_PLL_FREQUENCY,
|
||||||
NOTIFICATION_RECORDING_FILE_LOADED,
|
NOTIFICATION_RECORDING_FILE_LOADED,
|
||||||
NOTIFICATION_SAMPLE_RATE_CHANGE,
|
NOTIFICATION_SAMPLE_RATE_CHANGE,
|
||||||
NOTIFICATION_SQUELCH_THRESHOLD,
|
NOTIFICATION_SQUELCH_THRESHOLD,
|
||||||
@@ -47,7 +49,6 @@ public class SourceEvent
|
|||||||
NOTIFICATION_TUNER_SHUTDOWN,
|
NOTIFICATION_TUNER_SHUTDOWN,
|
||||||
NOTIFICATION_ERROR_STATE,
|
NOTIFICATION_ERROR_STATE,
|
||||||
|
|
||||||
REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE,
|
|
||||||
REQUEST_FREQUENCY_CHANGE,
|
REQUEST_FREQUENCY_CHANGE,
|
||||||
REQUEST_FREQUENCY_ROTATION,
|
REQUEST_FREQUENCY_ROTATION,
|
||||||
REQUEST_FREQUENCY_SELECTION,
|
REQUEST_FREQUENCY_SELECTION,
|
||||||
@@ -242,13 +243,13 @@ public class SourceEvent
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new channel frequency correction change event
|
* Creates a new PLL frequency error measurement notification event.
|
||||||
*
|
*
|
||||||
* @param frequencyCorrection in hertz
|
* @param frequencyError in hertz
|
||||||
*/
|
*/
|
||||||
public static SourceEvent channelFrequencyCorrectionChange(long frequencyCorrection)
|
public static SourceEvent pllFrequencyMeasurement(long frequencyError)
|
||||||
{
|
{
|
||||||
return new SourceEvent(Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequencyCorrection);
|
return new SourceEvent(Event.NOTIFICATION_PLL_FREQUENCY, frequencyError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -332,16 +333,6 @@ public class SourceEvent
|
|||||||
return new SourceEvent(Event.REQUEST_FREQUENCY_CHANGE, frequency);
|
return new SourceEvent(Event.REQUEST_FREQUENCY_CHANGE, frequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new channel frequency correction change request event
|
|
||||||
*
|
|
||||||
* @param frequency requested
|
|
||||||
*/
|
|
||||||
public static SourceEvent channelFrequencyCorrectionRequest(long frequency)
|
|
||||||
{
|
|
||||||
return new SourceEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, frequency);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new start sample stream request event. This method uses the current system time in
|
* Creates a new start sample stream request event. This method uses the current system time in
|
||||||
* milliseconds as the requested sample start time.
|
* milliseconds as the requested sample start time.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -50,9 +50,7 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
private IRealDecimationFilter mIDecimationFilter;
|
private IRealDecimationFilter mIDecimationFilter;
|
||||||
private IRealDecimationFilter mQDecimationFilter;
|
private IRealDecimationFilter mQDecimationFilter;
|
||||||
private double mChannelSampleRate;
|
private double mChannelSampleRate;
|
||||||
private long mChannelFrequencyCorrection = 0;
|
|
||||||
private long mTunerFrequency;
|
private long mTunerFrequency;
|
||||||
private Listener<ComplexSamples> mListener;
|
|
||||||
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,38 +129,25 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
updateMixerFrequencyOffset();
|
updateMixerFrequencyOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Current frequency correction being applied to the channel sample stream
|
|
||||||
*
|
|
||||||
* @return correction in hertz
|
|
||||||
*/
|
|
||||||
public long getFrequencyCorrection()
|
|
||||||
{
|
|
||||||
return mChannelFrequencyCorrection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the frequency correction value and broadcasts the change to the registered downstream listener.
|
|
||||||
*
|
|
||||||
* @param correction current frequency correction value.
|
|
||||||
*/
|
|
||||||
public void setFrequencyCorrection(long correction)
|
|
||||||
{
|
|
||||||
mChannelFrequencyCorrection = correction;
|
|
||||||
updateMixerFrequencyOffset();
|
|
||||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the local mixer frequency offset from the tuned frequency,
|
* Calculates the local mixer frequency offset from the tuned frequency,
|
||||||
* channel's requested frequency, and channel frequency correction.
|
* channel's requested frequency, and channel frequency correction.
|
||||||
*/
|
*/
|
||||||
private void updateMixerFrequencyOffset()
|
private void updateMixerFrequencyOffset()
|
||||||
{
|
{
|
||||||
long offset = mTunerFrequency - getTunerChannel().getFrequency() - mChannelFrequencyCorrection;
|
long offset = mTunerFrequency - getTunerChannel().getFrequency();
|
||||||
mFrequencyCorrectionMixer.setFrequency(offset);
|
mFrequencyCorrectionMixer.setFrequency(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixer frequency applying offset to the samples.
|
||||||
|
* @return mixer frequency
|
||||||
|
*/
|
||||||
|
public long getMixerFrequency()
|
||||||
|
{
|
||||||
|
return (long)mFrequencyCorrectionMixer.getFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the sample rate of the incoming sample stream from the producer
|
* Sets the sample rate of the incoming sample stream from the producer
|
||||||
*
|
*
|
||||||
@@ -174,28 +159,6 @@ public class HalfBandTunerChannelSource<T extends INativeBuffer> extends TunerCh
|
|||||||
//Not implemented. Sample rate changes are not permitted once sample stream starts
|
//Not implemented. Sample rate changes are not permitted once sample stream starts
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the frequency correction for the outbound sample stream to the consumer
|
|
||||||
*
|
|
||||||
* @param correction in hertz
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected void setChannelFrequencyCorrection(long correction)
|
|
||||||
{
|
|
||||||
mChannelFrequencyCorrection = correction;
|
|
||||||
broadcastConsumerSourceEvent(SourceEvent.channelFrequencyCorrectionChange(correction));
|
|
||||||
updateMixerFrequencyOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frequency correction value currently being applied to the outbound sample stream
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long getChannelFrequencyCorrection()
|
|
||||||
{
|
|
||||||
return mChannelFrequencyCorrection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the listener to receive the complex buffer sample output from this channel
|
* Sets the listener to receive the complex buffer sample output from this channel
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,13 +29,12 @@ import io.github.dsheirer.source.heartbeat.Heartbeat;
|
|||||||
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multiple-frequency tuner channel source. Provides a wrapper around a tuner channel source and listens for external
|
* Multiple-frequency tuner channel source. Provides a wrapper around a tuner channel source and listens for external
|
||||||
@@ -273,26 +272,6 @@ public class MultiFrequencyTunerChannelSource extends TunerChannelSource
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setChannelFrequencyCorrection(long correction)
|
|
||||||
{
|
|
||||||
if(mTunerChannelSource != null)
|
|
||||||
{
|
|
||||||
mTunerChannelSource.setChannelFrequencyCorrection(correction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getChannelFrequencyCorrection()
|
|
||||||
{
|
|
||||||
if(mTunerChannelSource != null)
|
|
||||||
{
|
|
||||||
return mTunerChannelSource.getChannelFrequencyCorrection();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setListener(Listener<ComplexSamples> complexSamplesListener)
|
public void setListener(Listener<ComplexSamples> complexSamplesListener)
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user