mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
#1422 Resolves issue where MotoTRBO IP Site Connect and Capacity Plus voice group user message not decoding Radio & Talkgroup IDs correctly. Creates standalone application for viewing DMR messages from recordings. (#1442)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
@@ -34,7 +31,7 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
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 ) */
|
||||
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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,12 +20,16 @@
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
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
|
||||
*/
|
||||
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_1 = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||
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 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)
|
||||
{
|
||||
int[] input = new int[255];
|
||||
int[] output = new int[255];
|
||||
|
||||
input[0] = message.getInt(FLC_PARITY_2) ^ crcMask;
|
||||
input[1] = message.getInt(FLC_PARITY_1) ^ crcMask;
|
||||
input[2] = message.getInt(FLC_PARITY_0) ^ crcMask;
|
||||
input[0] = (message.getInt(FLC_PARITY_2) ^ crcMask);
|
||||
input[1] = (message.getInt(FLC_PARITY_1) ^ crcMask);
|
||||
input[2] = (message.getInt(FLC_PARITY_0) ^ crcMask);
|
||||
input[3] = message.getInt(FLC_CODEWORD_8);
|
||||
input[4] = message.getInt(FLC_CODEWORD_7);
|
||||
input[5] = message.getInt(FLC_CODEWORD_6);
|
||||
@@ -106,4 +110,40 @@ public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
234
src/main/java/io/github/dsheirer/gui/dmr/DMRMessageViewer.java
Normal file
234
src/main/java/io/github/dsheirer/gui/dmr/DMRMessageViewer.java
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.prefs.Preferences;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
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;
|
||||
|
||||
public class DMRMessageViewer extends Application
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DMRMessageViewer.class);
|
||||
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory";
|
||||
private Preferences mPreferences = Preferences.userNodeForPackage(DMRMessageViewer.class);
|
||||
private Stage mStage;
|
||||
private Scene mScene;
|
||||
private VBox mContentBox;
|
||||
private Button mSelectFileButton;
|
||||
private Label mSelectedFileLabel;
|
||||
private TableView<IMessage> mMessageTableView;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception
|
||||
{
|
||||
mStage = primaryStage;
|
||||
mStage.setTitle("Recording Message Viewer (.bits)");
|
||||
mStage.setScene(getScene());
|
||||
mStage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
getMessageTableView().getItems().clear();
|
||||
|
||||
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
|
||||
DecodeConfigDMR config = new DecodeConfigDMR();
|
||||
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
|
||||
messageFramer.setListener(messageProcessor);
|
||||
messageProcessor.setMessageListener(message -> getMessageTableView().getItems().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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene for this application
|
||||
*/
|
||||
private Scene getScene()
|
||||
{
|
||||
if(mScene == null)
|
||||
{
|
||||
mScene = new Scene(getContent(), 1100, 800);
|
||||
}
|
||||
|
||||
return mScene;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary content pane.
|
||||
*/
|
||||
private VBox getContent()
|
||||
{
|
||||
if(mContentBox == null)
|
||||
{
|
||||
mContentBox = new VBox();
|
||||
mContentBox.setPadding(new Insets(5));
|
||||
mContentBox.setSpacing(5);
|
||||
HBox fileBox = new HBox();
|
||||
fileBox.setSpacing(5);
|
||||
HBox.setHgrow(getSelectFileButton(), Priority.NEVER);
|
||||
HBox.setHgrow(getSelectedFileLabel(), Priority.ALWAYS);
|
||||
getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER);
|
||||
fileBox.getChildren().addAll(getSelectFileButton(), getSelectedFileLabel());
|
||||
|
||||
VBox.setVgrow(fileBox, Priority.NEVER);
|
||||
VBox.setVgrow(getMessageTableView(), Priority.ALWAYS);
|
||||
|
||||
mContentBox.getChildren().addAll(fileBox, getMessageTableView());
|
||||
}
|
||||
|
||||
return mContentBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* List view control with DMR messages
|
||||
*/
|
||||
private TableView<IMessage> getMessageTableView()
|
||||
{
|
||||
if(mMessageTableView == null)
|
||||
{
|
||||
mMessageTableView = new TableView<>();
|
||||
|
||||
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(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(mStage);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,6 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
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.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||
@@ -71,9 +72,23 @@ public class LCMessageFactory
|
||||
throw new IllegalArgumentException("Message cannot be null");
|
||||
}
|
||||
|
||||
//RS(12,9,4) can correct up to floor(4/2) = 1 bit error.
|
||||
boolean corrected = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message,
|
||||
isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
|
||||
boolean valid = true;
|
||||
|
||||
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);
|
||||
mLog.warn("Doing the reed solomon thing .... valid: " + valid);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Unrecognized Link Control Message Size: " + message.size());
|
||||
}
|
||||
|
||||
LCOpcode opcode = FullLCMessage.getOpcode(message);
|
||||
|
||||
@@ -118,7 +133,8 @@ public class LCMessageFactory
|
||||
break;
|
||||
}
|
||||
|
||||
flc.setValid(corrected);
|
||||
flc.setValid(valid);
|
||||
|
||||
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
|
||||
* 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()
|
||||
{
|
||||
@@ -133,6 +133,7 @@ public class FLCAssembler
|
||||
for(int row = 0; row < 8; row++)
|
||||
{
|
||||
int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16);
|
||||
|
||||
errorCount += rowErrorCount;
|
||||
|
||||
if(errorCount > 1)
|
||||
@@ -179,6 +180,9 @@ public class FLCAssembler
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -99,4 +99,13 @@ public abstract class FullLCMessage extends LCMessage
|
||||
{
|
||||
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
|
||||
* 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.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -69,6 +68,7 @@ public class GroupVoiceChannelUser extends AbstractVoiceChannelUser
|
||||
sb.append(getRadio());
|
||||
sb.append(" TO:").append(getTalkgroup());
|
||||
sb.append(" ").append(getServiceOptions());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,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.identifier.DMRRadio;
|
||||
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.List;
|
||||
|
||||
/**
|
||||
* 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_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 RadioIdentifier mRadio;
|
||||
@@ -69,20 +71,33 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
sb.append("FLC MOTOROLA CAP+ GROUP VOICE CHANNEL USER FM:");
|
||||
sb.append(getRadio());
|
||||
sb.append(" TO:").append(getTalkgroup());
|
||||
sb.append(" ON CHANNEL:");
|
||||
if(hasVoiceChannel())
|
||||
if(isEncrypted())
|
||||
{
|
||||
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
|
||||
{
|
||||
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(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
@@ -119,6 +134,7 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
||||
|
||||
/**
|
||||
* Rest timeslot
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getVoiceChannelTimeslot()
|
||||
@@ -131,7 +147,7 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
||||
*/
|
||||
public boolean hasVoiceChannel()
|
||||
{
|
||||
return getVoiceChannelRepeater() != 0;
|
||||
return getServiceOptions().isCapacityPlus() && getVoiceChannelRepeater() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +157,14 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
||||
{
|
||||
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;
|
||||
@@ -154,7 +177,14 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
||||
{
|
||||
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;
|
||||
@@ -189,12 +219,13 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
|
||||
|
||||
/**
|
||||
* Applies the LSN to frequency map to the rest channel.
|
||||
*
|
||||
* @param timeslotFrequencies that match the logical timeslots
|
||||
*/
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
||||
{
|
||||
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
|
||||
* 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.identifier.DMRRadio;
|
||||
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.List;
|
||||
|
||||
/**
|
||||
* 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[] 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_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[] 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 TalkgroupIdentifier mTalkgroup;
|
||||
@@ -70,6 +68,16 @@ public class CapacityPlusWideAreaVoiceChannelUser extends AbstractVoiceChannelUs
|
||||
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(getRadio());
|
||||
sb.append(" TO:").append(getTalkgroup());
|
||||
@@ -102,7 +110,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends AbstractVoiceChannelUs
|
||||
*/
|
||||
public int getUnknown2()
|
||||
{
|
||||
return getMessage().getInt(UNKNOWN_2);
|
||||
return getMessage().getInt(UNKNOWN_3);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
* 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;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,6 +30,8 @@ public class ServiceOptions
|
||||
{
|
||||
private static final int EMERGENCY_MASK = 0x80;
|
||||
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 OPEN_VOICE_CALL_MODE_MASK = 0x4;
|
||||
private static final int PRIORITY_MASK = 0x3;
|
||||
@@ -77,6 +78,24 @@ public class ServiceOptions
|
||||
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
|
||||
* @return priority 0-3, 0 = lowest/routine, 3 = highest
|
||||
@@ -119,6 +138,16 @@ public class ServiceOptions
|
||||
flags.add("OVCM");
|
||||
}
|
||||
|
||||
if(isReserved1())
|
||||
{
|
||||
flags.add("RSVD1");
|
||||
}
|
||||
|
||||
if(isReserved2())
|
||||
{
|
||||
flags.add("RSVD2");
|
||||
}
|
||||
|
||||
if(getPriority() > 0)
|
||||
{
|
||||
flags.add("PRIORITY-" + getPriority());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -18,9 +18,6 @@
|
||||
*/
|
||||
package io.github.dsheirer.record.binary;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -29,6 +26,8 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BinaryReader implements Iterator<ByteBuffer>, AutoCloseable
|
||||
{
|
||||
@@ -94,7 +93,7 @@ public class BinaryReader implements Iterator<ByteBuffer>, AutoCloseable
|
||||
byte[] readBytes = new byte[mBufferSize];
|
||||
int bytesRead = mInputStream.read(readBytes);
|
||||
|
||||
if(bytesRead < readBytes.length)
|
||||
if(bytesRead < readBytes.length && bytesRead > 0)
|
||||
{
|
||||
readBytes = Arrays.copyOf(readBytes, bytesRead);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user