mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 09:37:36 -05:00
* P25 Phase 2 now supports control channel decoding and decoder was enhanced to improve decodes. * Radio reference site editor updated for phase 2 sites to allow user to select FDMA or TDMA control. * Decode events are now minimized and de-duplicated. * Fully parsing all phase 1/2 messages from the latest ICD. * Vendor specific messaging recoveries (Moto & Harris). * Patch group management enhancements with stale patch detection. * Tuner editor now fully resets the min/max frequency values * Default traffic channel count increased to 20 (from 3). * ISSI roaming radio and talkgroup values now displayed and aliasable as fully qualified values (e.g. 1234(123.456.7890) * Moto emergency alarm activation messaging support * Expanded list of P25 encryption algorithms * Message (bits) viewer enhanced for P25 phase 1 and phase 2 * IPV4 addresses and UDP ports no longer classified as user values - won't display in event views. * New framework for describing message binary fields that should reduce overall memory footprint and enhance code readability Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -29,6 +29,7 @@
|
|||||||
<w>talkgroups</w>
|
<w>talkgroups</w>
|
||||||
<w>tgid</w>
|
<w>tgid</w>
|
||||||
<w>ultravox</w>
|
<w>ultravox</w>
|
||||||
|
<w>unsynchronized</w>
|
||||||
<w>wacn</w>
|
<w>wacn</w>
|
||||||
<w>xtea</w>
|
<w>xtea</w>
|
||||||
</words>
|
</words>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -663,11 +663,13 @@ public class AliasList
|
|||||||
|
|
||||||
public Alias getAlias(TalkgroupIdentifier identifier)
|
public Alias getAlias(TalkgroupIdentifier identifier)
|
||||||
{
|
{
|
||||||
|
//Attempt to do a fully qualified identifier match first.
|
||||||
if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti)
|
if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti)
|
||||||
{
|
{
|
||||||
return mFullyQualifiedTalkgroupAliasMap.get(fqti.toString());
|
return mFullyQualifiedTalkgroupAliasMap.get(fqti.getFullyQualifiedTalkgroupAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Then try to match it by it's locally assigned (temporary) address.
|
||||||
int value = identifier.getValue();
|
int value = identifier.getValue();
|
||||||
|
|
||||||
Alias mapValue = mTalkgroupAliasMap.get(value);
|
Alias mapValue = mTalkgroupAliasMap.get(value);
|
||||||
@@ -676,6 +678,7 @@ public class AliasList
|
|||||||
return mapValue;
|
return mapValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Finally, match the locally assigned address against any talkgroup ranges
|
||||||
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
|
for(Map.Entry<TalkgroupRange, Alias> entry : mTalkgroupRangeAliasMap.entrySet())
|
||||||
{
|
{
|
||||||
if(entry.getKey().contains(value))
|
if(entry.getKey().contains(value))
|
||||||
@@ -780,11 +783,13 @@ public class AliasList
|
|||||||
|
|
||||||
public Alias getAlias(RadioIdentifier identifier)
|
public Alias getAlias(RadioIdentifier identifier)
|
||||||
{
|
{
|
||||||
|
//Attempt to do a fully qualified identifier match first.
|
||||||
if(identifier instanceof FullyQualifiedRadioIdentifier fqri)
|
if(identifier instanceof FullyQualifiedRadioIdentifier fqri)
|
||||||
{
|
{
|
||||||
return mFullyQualifiedRadioAliasMap.get(fqri.toString());
|
return mFullyQualifiedRadioAliasMap.get(fqri.getFullyQualifiedRadioAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Then match against the locally assigned (temporary) address
|
||||||
int value = identifier.getValue();
|
int value = identifier.getValue();
|
||||||
|
|
||||||
Alias mapValue = mRadioAliasMap.get(value);
|
Alias mapValue = mRadioAliasMap.get(value);
|
||||||
@@ -793,6 +798,7 @@ public class AliasList
|
|||||||
return mapValue;
|
return mapValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Finally, attempt to match the locally assigned (temporary) address against any radio ranges.
|
||||||
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
|
for(Map.Entry<RadioRange, Alias> entry : mRadioRangeAliasMap.entrySet())
|
||||||
{
|
{
|
||||||
if(entry.getKey().contains(value))
|
if(entry.getKey().contains(value))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -451,6 +451,203 @@ public class BinaryMessage extends BitSet
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integer value of the message field described by the field argument.
|
||||||
|
* @param intField with start and end indices (inclusive).
|
||||||
|
* @return integer field value.
|
||||||
|
*/
|
||||||
|
public int getInt(IntField intField)
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
for(int index = intField.start(); index <= intField.end(); index++)
|
||||||
|
{
|
||||||
|
value = Integer.rotateLeft(value, 1);
|
||||||
|
|
||||||
|
if(get(index))
|
||||||
|
{
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integer value of the message field described by the field argument.
|
||||||
|
* @param fragmentedField with an array of message indices
|
||||||
|
* @return integer field value.
|
||||||
|
*/
|
||||||
|
public int getInt(FragmentedIntField fragmentedField)
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
for(int index: fragmentedField.indices())
|
||||||
|
{
|
||||||
|
value = Integer.rotateLeft(value, 1);
|
||||||
|
|
||||||
|
if(get(index))
|
||||||
|
{
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
||||||
|
* @param intField to inspect
|
||||||
|
* @return true if any of the bits are set, indicating a non-zero value.
|
||||||
|
*/
|
||||||
|
public boolean hasInt(IntField intField)
|
||||||
|
{
|
||||||
|
return nextSetBit(intField.start()) <= intField.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
||||||
|
* @param intField to inspect
|
||||||
|
* @return true if any of the bits are set, indicating a non-zero value.
|
||||||
|
*/
|
||||||
|
public boolean hasInt(FragmentedIntField fragmentedField)
|
||||||
|
{
|
||||||
|
for(int index: fragmentedField.indices())
|
||||||
|
{
|
||||||
|
if(get(index))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integer value of the message field described by the field argument where the message start index is
|
||||||
|
* offset within this binary message.
|
||||||
|
* @param intField with start and end indices (inclusive).
|
||||||
|
* @param offset to the start of the first message index.
|
||||||
|
* @return integer field value.
|
||||||
|
*/
|
||||||
|
public int getInt(IntField intField, int offset)
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
for(int index = intField.start() + offset; index <= intField.end() + offset; index++)
|
||||||
|
{
|
||||||
|
value = Integer.rotateLeft(value, 1);
|
||||||
|
|
||||||
|
if(get(index))
|
||||||
|
{
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integer value of the message field described by the field argument where the message start index is
|
||||||
|
* offset within this binary message.
|
||||||
|
* @param fragmentedField with an array of field indices
|
||||||
|
* @param offset to the start of the first message index.
|
||||||
|
* @return integer field value.
|
||||||
|
*/
|
||||||
|
public int getInt(FragmentedIntField fragmentedField, int offset)
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
for(int index: fragmentedField.indices())
|
||||||
|
{
|
||||||
|
value = Integer.rotateLeft(value, 1);
|
||||||
|
|
||||||
|
if(get(index + offset))
|
||||||
|
{
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
||||||
|
* @param intField to inspect
|
||||||
|
* @param offset to the first bit of the message.
|
||||||
|
* @return true if any of the bits are set, indicating a non-zero value.
|
||||||
|
*/
|
||||||
|
public boolean hasInt(IntField intField, int offset)
|
||||||
|
{
|
||||||
|
return nextSetBit(intField.start() + offset) <= (intField.end() + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the message field contains a non-zero value indicated by any bits set in the int field indices.
|
||||||
|
* @param intField to inspect
|
||||||
|
* @param offset to the first bit of the message.
|
||||||
|
* @return true if any of the bits are set, indicating a non-zero value.
|
||||||
|
*/
|
||||||
|
public boolean hasInt(FragmentedIntField fragmentedField, int offset)
|
||||||
|
{
|
||||||
|
for(int index: fragmentedField.indices())
|
||||||
|
{
|
||||||
|
if(get(index + offset))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the long value of the message field described by the field argument.
|
||||||
|
* @param intField with start and end indices (inclusive).
|
||||||
|
* @return integer field value.
|
||||||
|
*/
|
||||||
|
public long getLong(LongField intField)
|
||||||
|
{
|
||||||
|
long value = 0;
|
||||||
|
|
||||||
|
for(int index = intField.start(); index <= intField.end(); index++)
|
||||||
|
{
|
||||||
|
value = Long.rotateLeft(value, 1);
|
||||||
|
|
||||||
|
if(get(index))
|
||||||
|
{
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the long value of the message field described by the field argument where the message start index is
|
||||||
|
* offset within this binary message.
|
||||||
|
* @param intField with start and end indices (inclusive).
|
||||||
|
* @param offset to the start of the first message index.
|
||||||
|
* @return field value.
|
||||||
|
*/
|
||||||
|
public long getLong(LongField intField, int offset)
|
||||||
|
{
|
||||||
|
long value = 0;
|
||||||
|
|
||||||
|
for(int index = intField.start() + offset; index <= intField.end() + offset; index++)
|
||||||
|
{
|
||||||
|
value = Long.rotateLeft(value, 1);
|
||||||
|
|
||||||
|
if(get(index))
|
||||||
|
{
|
||||||
|
value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the integer value represented by the bit array
|
* Returns the integer value represented by the bit array
|
||||||
*
|
*
|
||||||
@@ -742,6 +939,18 @@ public class BinaryMessage extends BitSet
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the integer field formatted as a hex value using zero prefixes to pad the hex character count to fully
|
||||||
|
* represent the size (width) of the field.
|
||||||
|
* @param field to parse as hex
|
||||||
|
* @return hex value.
|
||||||
|
*/
|
||||||
|
public String getHex(IntField field)
|
||||||
|
{
|
||||||
|
int width = Math.ceilDiv(field.width(), 4);
|
||||||
|
return String.format("%0" + width + "X", getInt(field));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less
|
* Format the byte value that starts at the specified index as hexadecimal. If the length of the message is less
|
||||||
* than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in
|
* than the start index plus 7 bits, then the value represents those bits as high-order bits with zero padding in
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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;
|
||||||
|
|
||||||
@@ -84,9 +81,9 @@ public class CorrectedBinaryMessage extends BinaryMessage
|
|||||||
/**
|
/**
|
||||||
* Returns a new binary message containing the bits from (inclusive) to end (exclusive).
|
* Returns a new binary message containing the bits from (inclusive) to end (exclusive).
|
||||||
*
|
*
|
||||||
* @param start bit
|
* @param start bit inclusive
|
||||||
* @param end bit
|
* @param end bit exclusive
|
||||||
* @return message
|
* @return message with sub message bits
|
||||||
*/
|
*/
|
||||||
public CorrectedBinaryMessage getSubMessage(int start, int end)
|
public CorrectedBinaryMessage getSubMessage(int start, int end)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.bits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a fragmented or non-contiguous bit field within a binary message.
|
||||||
|
* @param indices for the bits in the field.
|
||||||
|
*/
|
||||||
|
public record FragmentedIntField(int... indices)
|
||||||
|
{
|
||||||
|
public FragmentedIntField
|
||||||
|
{
|
||||||
|
if(indices.length > 32)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Integer field indices size [" + indices.length + "] cannot exceed 32-bits for an integer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method.
|
||||||
|
* @param indices (inclusive)
|
||||||
|
* @return constructed fragmented integer field.
|
||||||
|
*/
|
||||||
|
public static FragmentedIntField of(int... indices)
|
||||||
|
{
|
||||||
|
return new FragmentedIntField(indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
131
src/main/java/io/github/dsheirer/bits/IntField.java
Normal file
131
src/main/java/io/github/dsheirer/bits/IntField.java
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.bits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a contiguous bit field within a binary message.
|
||||||
|
* @param start bit index, inclusive.
|
||||||
|
* @param end bit index, inclusive.
|
||||||
|
*/
|
||||||
|
public record IntField(int start, int end)
|
||||||
|
{
|
||||||
|
public IntField
|
||||||
|
{
|
||||||
|
if(end - start > 32)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Integer field length [" + (end - start) + "] cannot exceed 32-bits for an integer");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(end < start)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Integer field start index must be less than end index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Width of the field in bit positions
|
||||||
|
* @return width
|
||||||
|
*/
|
||||||
|
public int width()
|
||||||
|
{
|
||||||
|
return end() - start() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @param end index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static IntField range(int start, int end)
|
||||||
|
{
|
||||||
|
return new IntField(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with one four bits of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static IntField length4(int start)
|
||||||
|
{
|
||||||
|
return new IntField(start, (start + 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with one octet of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static IntField length8(int start)
|
||||||
|
{
|
||||||
|
return new IntField(start, (start + 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with 12 bits of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static IntField length12(int start)
|
||||||
|
{
|
||||||
|
return new IntField(start, (start + 11));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with two octets of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static IntField length16(int start)
|
||||||
|
{
|
||||||
|
return new IntField(start, (start + 15));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with 20 bits of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static IntField length20(int start)
|
||||||
|
{
|
||||||
|
return new IntField(start, (start + 19));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with two octets of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static IntField length24(int start)
|
||||||
|
{
|
||||||
|
return new IntField(start, (start + 23));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with three octets of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static IntField length32(int start)
|
||||||
|
{
|
||||||
|
return new IntField(start, (start + 31));
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/main/java/io/github/dsheirer/bits/LongField.java
Normal file
122
src/main/java/io/github/dsheirer/bits/LongField.java
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.bits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a contiguous bit field within a binary message.
|
||||||
|
* @param start bit index, inclusive.
|
||||||
|
* @param end bit index, inclusive.
|
||||||
|
*/
|
||||||
|
public record LongField(int start, int end)
|
||||||
|
{
|
||||||
|
public LongField
|
||||||
|
{
|
||||||
|
if(end - start > 64)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Long field length [" + (end - start) + "] cannot exceed 64-bits for a long");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(end < start)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Long field start index must be less than end index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @param end index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static LongField range(int start, int end)
|
||||||
|
{
|
||||||
|
return new LongField(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with one four bits of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static LongField length4(int start)
|
||||||
|
{
|
||||||
|
return new LongField(start, (start + 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with one octet of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static LongField length8(int start)
|
||||||
|
{
|
||||||
|
return new LongField(start, (start + 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with 12 bits of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static LongField length12(int start)
|
||||||
|
{
|
||||||
|
return new LongField(start, (start + 11));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with two octets of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static LongField length16(int start)
|
||||||
|
{
|
||||||
|
return new LongField(start, (start + 15));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with 20 bits of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static LongField length20(int start)
|
||||||
|
{
|
||||||
|
return new LongField(start, (start + 19));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with two octets of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static LongField length24(int start)
|
||||||
|
{
|
||||||
|
return new LongField(start, (start + 23));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility constructor method for a field with three octets of length.
|
||||||
|
* @param start index (inclusive)
|
||||||
|
* @return constructed bit field.
|
||||||
|
*/
|
||||||
|
public static LongField length32(int start)
|
||||||
|
{
|
||||||
|
return new LongField(start, (start + 31));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import io.github.dsheirer.message.IMessageListener;
|
|||||||
import io.github.dsheirer.module.Module;
|
import io.github.dsheirer.module.Module;
|
||||||
import io.github.dsheirer.module.decode.DecoderType;
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
import io.github.dsheirer.module.decode.event.ActivitySummaryProvider;
|
import io.github.dsheirer.module.decode.event.ActivitySummaryProvider;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEventDuplicateDetector;
|
||||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
|
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
@@ -44,6 +45,7 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
|
|||||||
protected Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
|
protected Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
|
||||||
protected Listener<DecoderStateEvent> mDecoderStateListener;
|
protected Listener<DecoderStateEvent> mDecoderStateListener;
|
||||||
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
|
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
|
||||||
|
private static final DecodeEventDuplicateDetector mDuplicateEventDetector = new DecodeEventDuplicateDetector();
|
||||||
private boolean mRunning;
|
private boolean mRunning;
|
||||||
|
|
||||||
public abstract DecoderType getDecoderType();
|
public abstract DecoderType getDecoderType();
|
||||||
@@ -105,7 +107,10 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
|
|||||||
*/
|
*/
|
||||||
protected void broadcast(IDecodeEvent event)
|
protected void broadcast(IDecodeEvent event)
|
||||||
{
|
{
|
||||||
mDecodeEventBroadcaster.broadcast(event);
|
if(!mDuplicateEventDetector.isDuplicate(event, System.currentTimeMillis()))
|
||||||
|
{
|
||||||
|
mDecodeEventBroadcaster.broadcast(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,6 +166,9 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
|
|||||||
mDecoderStateListener = null;
|
mDecoderStateListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper that implements the IDecoderStateEventListener interface
|
||||||
|
*/
|
||||||
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
|
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@@ -169,23 +177,4 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
|
|||||||
receiveDecoderStateEvent(event);
|
receiveDecoderStateEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Message listener that only passes messages while we're running. This is important because each of the decoders
|
|
||||||
* can process blocks of samples and that can result in additional messages being generated even after shutdown
|
|
||||||
* and so we shut off the processing of decoded messages when we're commanded to stop. The sample processing thread
|
|
||||||
* cannot be shutdown or forcefully interrupted because downstream decoder and channel states may have acquired
|
|
||||||
* locks that have to be properly released.
|
|
||||||
*/
|
|
||||||
private class MessageListener implements Listener<IMessage>
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void receive(IMessage message)
|
|
||||||
{
|
|
||||||
if(isRunning())
|
|
||||||
{
|
|
||||||
receive(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
|||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
|
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
|
||||||
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
|
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,13 +39,21 @@ public abstract class DecoderState extends AbstractDecoderState
|
|||||||
private MutableIdentifierCollection mIdentifierCollection;
|
private MutableIdentifierCollection mIdentifierCollection;
|
||||||
protected Listener<IdentifierUpdateNotification> mConfigurationIdentifierListener;
|
protected Listener<IdentifierUpdateNotification> mConfigurationIdentifierListener;
|
||||||
protected IChannelDescriptor mCurrentChannel;
|
protected IChannelDescriptor mCurrentChannel;
|
||||||
|
private long mCurrentFrequency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param mutableIdentifierCollection to preload into this decoder state
|
||||||
|
*/
|
||||||
public DecoderState(MutableIdentifierCollection mutableIdentifierCollection)
|
public DecoderState(MutableIdentifierCollection mutableIdentifierCollection)
|
||||||
{
|
{
|
||||||
mIdentifierCollection = mutableIdentifierCollection;
|
mIdentifierCollection = mutableIdentifierCollection;
|
||||||
mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType()));
|
mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance using an empty identifier collection.
|
||||||
|
*/
|
||||||
public DecoderState()
|
public DecoderState()
|
||||||
{
|
{
|
||||||
this(new MutableIdentifierCollection());
|
this(new MutableIdentifierCollection());
|
||||||
@@ -59,6 +68,23 @@ public abstract class DecoderState extends AbstractDecoderState
|
|||||||
mIdentifierCollection.broadcastIdentifiers();
|
mIdentifierCollection.broadcastIdentifiers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current frequency for this channel.
|
||||||
|
*/
|
||||||
|
public long getCurrentFrequency()
|
||||||
|
{
|
||||||
|
return mCurrentFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current frequency for this channel.
|
||||||
|
* @param frequency to set.
|
||||||
|
*/
|
||||||
|
public void setCurrentFrequency(long frequency)
|
||||||
|
{
|
||||||
|
mCurrentFrequency = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the listener to receive identifier update notifications
|
* Registers the listener to receive identifier update notifications
|
||||||
*/
|
*/
|
||||||
@@ -153,9 +179,13 @@ public abstract class DecoderState extends AbstractDecoderState
|
|||||||
{
|
{
|
||||||
getIdentifierCollection().receive(identifierUpdateNotification);
|
getIdentifierCollection().receive(identifierUpdateNotification);
|
||||||
|
|
||||||
if(identifierUpdateNotification.getIdentifier() instanceof ChannelDescriptorConfigurationIdentifier)
|
if(identifierUpdateNotification.getIdentifier() instanceof ChannelDescriptorConfigurationIdentifier cdci)
|
||||||
{
|
{
|
||||||
setCurrentChannel(((ChannelDescriptorConfigurationIdentifier)identifierUpdateNotification.getIdentifier()).getValue());
|
setCurrentChannel(cdci.getValue());
|
||||||
|
}
|
||||||
|
else if(identifierUpdateNotification.getIdentifier() instanceof FrequencyConfigurationIdentifier fci)
|
||||||
|
{
|
||||||
|
setCurrentFrequency(fci.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,12 +26,20 @@ import io.github.dsheirer.identifier.IdentifierClass;
|
|||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
|
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base decoder state implementation for a multi-timeslot protocol that tracks the state for a single timeslot.
|
||||||
|
*/
|
||||||
public abstract class TimeslotDecoderState extends DecoderState
|
public abstract class TimeslotDecoderState extends DecoderState
|
||||||
{
|
{
|
||||||
private int mTimeslot;
|
private int mTimeslot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param timeslot to monitor/maintain state.
|
||||||
|
*/
|
||||||
public TimeslotDecoderState(int timeslot)
|
public TimeslotDecoderState(int timeslot)
|
||||||
{
|
{
|
||||||
super(new MutableIdentifierCollection(timeslot));
|
super(new MutableIdentifierCollection(timeslot));
|
||||||
@@ -39,6 +47,9 @@ public abstract class TimeslotDecoderState extends DecoderState
|
|||||||
mConfigurationIdentifierListener = new TimeslotConfigurationIdentifierListener();
|
mConfigurationIdentifierListener = new TimeslotConfigurationIdentifierListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitored timeslot for this decoder state instance.
|
||||||
|
*/
|
||||||
protected int getTimeslot()
|
protected int getTimeslot()
|
||||||
{
|
{
|
||||||
return mTimeslot;
|
return mTimeslot;
|
||||||
@@ -82,6 +93,10 @@ public abstract class TimeslotDecoderState extends DecoderState
|
|||||||
{
|
{
|
||||||
setCurrentChannel((IChannelDescriptor)identifier);
|
setCurrentChannel((IChannelDescriptor)identifier);
|
||||||
}
|
}
|
||||||
|
else if(identifier instanceof FrequencyConfigurationIdentifier fci)
|
||||||
|
{
|
||||||
|
setCurrentFrequency(fci.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
break;
|
break;
|
||||||
case REQUEST_DISABLE:
|
case REQUEST_DISABLE:
|
||||||
case NOTIFICATION_DELETE:
|
case NOTIFICATION_DELETE:
|
||||||
if(channel.isProcessing())
|
if(channel != null && channel.isProcessing())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
|
import io.github.dsheirer.log.LoggingSuppressor;
|
||||||
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 org.slf4j.Logger;
|
||||||
@@ -32,6 +33,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class BerlekempMassey
|
public class BerlekempMassey
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
|
private final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
|
||||||
|
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog);
|
||||||
|
|
||||||
/* Golay field size GF( 2 ** MM ) */
|
/* Golay field size GF( 2 ** MM ) */
|
||||||
private int MM;
|
private int MM;
|
||||||
@@ -234,7 +236,16 @@ public class BerlekempMassey
|
|||||||
/* put recd[i] into index form (ie as powers of alpha) */
|
/* put recd[i] into index form (ie as powers of alpha) */
|
||||||
for(int i = 0; i < NN; i++)
|
for(int i = 0; i < NN; i++)
|
||||||
{
|
{
|
||||||
output[i] = index_of[input[i]];
|
try
|
||||||
|
{
|
||||||
|
output[i] = index_of[input[i]];
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOGGING_SUPPRESSOR.error(getClass().toString(), 2, "Reed Solomon Decoder error for " +
|
||||||
|
"class [" + getClass() + "] there may be an issue with the message parser class indices - " +
|
||||||
|
"ensure the hex bit values are not larger 63", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* first form the syndromes */
|
/* first form the syndromes */
|
||||||
|
|||||||
285
src/main/java/io/github/dsheirer/edac/Binary40_9_16.java
Normal file
285
src/main/java/io/github/dsheirer/edac/Binary40_9_16.java
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.IntField;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P25 Phase 2 Binary (40, 9, 16) Code for IISCH in the Super Frame Fragment.
|
||||||
|
*/
|
||||||
|
public class Binary40_9_16
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(Binary40_9_16.class);
|
||||||
|
private static final long[] CHECKSUMS = {0x8816CE36D7l, 0x201DFD4F64l, 0x100F4B1758l, 0x0C00DED18El, 0x020807F7FFl,
|
||||||
|
0x09048D9B72l, 0x009DA3A171l, 0x0058CBAA4El, 0x00343D8597l};
|
||||||
|
private static final long CODE_WORD_OFFSET = 0x184229d461l;
|
||||||
|
private static final Map<Long,BinaryMessage> CODEWORD_MAP_TS1 = new TreeMap<>();
|
||||||
|
private static final Map<Long,BinaryMessage> CODEWORD_MAP_TS2 = new TreeMap<>();
|
||||||
|
private static final IntField CHANNEL_NUMBER = IntField.range(2, 3);
|
||||||
|
private static final IntField ISCH_LOCATION = IntField.range(4, 5);
|
||||||
|
private static final IntField WORD = IntField.range(0, 8);
|
||||||
|
|
||||||
|
public Binary40_9_16()
|
||||||
|
{
|
||||||
|
for(int x = 0; x < 512; x++)
|
||||||
|
{
|
||||||
|
BinaryMessage message = new BinaryMessage(9);
|
||||||
|
message.load(0, 9, x);
|
||||||
|
|
||||||
|
int channelNumber = message.getInt(CHANNEL_NUMBER) + 1;
|
||||||
|
int ischLocation = message.getInt(ISCH_LOCATION);
|
||||||
|
|
||||||
|
long codeword = getCodeWord(message);
|
||||||
|
codeword ^= CODE_WORD_OFFSET;
|
||||||
|
|
||||||
|
if(ischLocation != 3)
|
||||||
|
{
|
||||||
|
if(channelNumber == 1)
|
||||||
|
{
|
||||||
|
CODEWORD_MAP_TS1.put(codeword, message);
|
||||||
|
}
|
||||||
|
else if(channelNumber == 2)
|
||||||
|
{
|
||||||
|
CODEWORD_MAP_TS2.put(codeword, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> keys = new ArrayList<>(CODEWORD_MAP_TS1.keySet());
|
||||||
|
Collections.sort(keys);
|
||||||
|
for(Long key: keys)
|
||||||
|
{
|
||||||
|
BinaryMessage message = CODEWORD_MAP_TS1.get(key);
|
||||||
|
int distance = getDistance(key, keys);
|
||||||
|
}
|
||||||
|
List<Long> keys2 = new ArrayList<>(CODEWORD_MAP_TS2.keySet());
|
||||||
|
Collections.sort(keys2);
|
||||||
|
for(Long key: keys2)
|
||||||
|
{
|
||||||
|
BinaryMessage message = CODEWORD_MAP_TS2.get(key);
|
||||||
|
int distance = getDistance(key, keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the closest distance between the value and the values in the value set.
|
||||||
|
* @param value to test
|
||||||
|
* @param valueSet to test against
|
||||||
|
* @return closest distance of the value to the values in the value set, ignoring any equivalent values.
|
||||||
|
*/
|
||||||
|
public static int getDistance(long value, List<Long> valueSet)
|
||||||
|
{
|
||||||
|
int distance = 40;
|
||||||
|
|
||||||
|
for(Long candidate: valueSet)
|
||||||
|
{
|
||||||
|
if(candidate != value)
|
||||||
|
{
|
||||||
|
long syndrome = candidate ^ value;
|
||||||
|
int candidateDistance = Long.bitCount(syndrome);
|
||||||
|
if(candidateDistance < distance)
|
||||||
|
{
|
||||||
|
distance = candidateDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a codeword from the binary message where each set bit in the binary message corresponds to one of the
|
||||||
|
* words in the generator and we XOR the words together.
|
||||||
|
* @param message for generating a codeword
|
||||||
|
* @return codeword
|
||||||
|
*/
|
||||||
|
public static long getCodeWord(BinaryMessage message)
|
||||||
|
{
|
||||||
|
long codeword = 0;
|
||||||
|
|
||||||
|
for(int x = 0; x < message.length(); x++)
|
||||||
|
{
|
||||||
|
if(message.get(x))
|
||||||
|
{
|
||||||
|
codeword ^= CHECKSUMS[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the checksum (ie codeword) from the set bits in the message.
|
||||||
|
* @param message with set bits, 0-8
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static long calculateChecksum(BinaryMessage message)
|
||||||
|
{
|
||||||
|
long calculated = 0; //Starting value
|
||||||
|
|
||||||
|
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||||
|
for(int i = message.nextSetBit(0); i < 9; i = message.nextSetBit(i + 1))
|
||||||
|
{
|
||||||
|
calculated ^= CHECKSUMS[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculated;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Performs error detection and returns a corrected copy of the 24-bit
|
||||||
|
// * message that starts at the start index.
|
||||||
|
// *
|
||||||
|
// * @param message - source message containing startIndex + 24 bits length
|
||||||
|
// * @return - corrected 24-bit galois value
|
||||||
|
// */
|
||||||
|
// public static int checkAndCorrect(CorrectedBinaryMessage message)
|
||||||
|
// {
|
||||||
|
// boolean parityError = message.cardinality() % 2 != 0;
|
||||||
|
//
|
||||||
|
// long syndrome = getSyndrome(message);
|
||||||
|
//
|
||||||
|
// /* No errors */
|
||||||
|
// if(syndrome == 0)
|
||||||
|
// {
|
||||||
|
// if(parityError)
|
||||||
|
// {
|
||||||
|
// message.flip(23);
|
||||||
|
// message.incrementCorrectedBitCount(1);
|
||||||
|
// return 1;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /* Get original message value */
|
||||||
|
// int original = message.getInt(0, 22);
|
||||||
|
//
|
||||||
|
// int index = -1;
|
||||||
|
// int syndromeWeight = 3;
|
||||||
|
// int errors = 0;
|
||||||
|
//
|
||||||
|
// while(index < 23)
|
||||||
|
// {
|
||||||
|
// if(index != -1)
|
||||||
|
// {
|
||||||
|
// /* restore the previous flipped bit */
|
||||||
|
// if(index > 0)
|
||||||
|
// {
|
||||||
|
// message.flip(index - 1);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// message.flip(index);
|
||||||
|
//
|
||||||
|
// syndromeWeight = 2;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// syndrome = getSyndrome(message);
|
||||||
|
//
|
||||||
|
// if(syndrome > 0)
|
||||||
|
// {
|
||||||
|
// for(int i = 0; i < 23; i++)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// errors = Long.bitCount(syndrome);
|
||||||
|
//
|
||||||
|
// if(errors <= syndromeWeight)
|
||||||
|
// {
|
||||||
|
// message.xor(12, 11, syndrome);
|
||||||
|
//
|
||||||
|
// message.rotateRight(i, 0, 22);
|
||||||
|
//
|
||||||
|
// if(index >= 0)
|
||||||
|
// {
|
||||||
|
// errors++;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// int corrected = message.getInt(0, 22);
|
||||||
|
//
|
||||||
|
// if(Integer.bitCount(original ^ corrected) > 3)
|
||||||
|
// {
|
||||||
|
// return 2;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return 1;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// message.rotateLeft(0, 22);
|
||||||
|
// syndrome = getSyndrome(message);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// index++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return 2;
|
||||||
|
// }
|
||||||
|
|
||||||
|
private static long getSyndrome(BinaryMessage message)
|
||||||
|
{
|
||||||
|
long calculated = calculateChecksum(message);
|
||||||
|
long checksum = message.getInt(12, 22);
|
||||||
|
return (checksum ^ calculated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
// Binary40_9_16 edac = new Binary40_9_16();
|
||||||
|
|
||||||
|
long mask = 0xFFFFFFFFFFl;
|
||||||
|
long sync = 0x575D57F7FFl;
|
||||||
|
long syncLeft = Long.rotateLeft(sync, 2) & mask;
|
||||||
|
long syncRight = Long.rotateRight(sync, 2) & mask;
|
||||||
|
System.out.println("Mask: " + Long.toBinaryString(sync));
|
||||||
|
System.out.println("Left: " + Long.toBinaryString(syncLeft));
|
||||||
|
System.out.println("Right: " + Long.toBinaryString(syncRight));
|
||||||
|
|
||||||
|
int distanceLeft = Long.bitCount(sync ^ syncLeft);
|
||||||
|
int distanceRight = Long.bitCount( sync ^ syncRight);
|
||||||
|
|
||||||
|
System.out.println("Left: " + distanceLeft);
|
||||||
|
System.out.println("Right: " + distanceRight);
|
||||||
|
|
||||||
|
System.out.println("Finished!");
|
||||||
|
|
||||||
|
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20"));
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0"));
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00"));
|
||||||
|
//
|
||||||
|
// System.out.println("M:" + bm.toHexString());
|
||||||
|
// int a = Binary40_9_16.checkAndCorrect(bm);
|
||||||
|
// System.out.println("M:" + bm.toHexString());
|
||||||
|
//
|
||||||
|
// System.out.println("A:" + a);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdr-trunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
* Copyright (C) 2014-2024 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.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import java.util.Arrays;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -216,6 +220,63 @@ public class CRCP25
|
|||||||
0x20000000l, 0x40000000l, 0x80000000l
|
0x20000000l, 0x40000000l, 0x80000000l
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRC-12 checksums for P25 Phase 2 MAC PDU Contents. This is used for a 144-bit message and 12-bits of checksum
|
||||||
|
* using a polynomial of 0x1897 and generated by:
|
||||||
|
* <p>
|
||||||
|
* long[] checksums = CRCUtil.generate(144, 12, 0x1897l, 0x0l, false);
|
||||||
|
*/
|
||||||
|
private static int[] CRC_12_FACCH = new int[]{0x256, 0x12B, 0xCDE, 0x66F, 0xF7C, 0x7BE, 0x3DF, 0xDA4, 0x6D2, 0x369,
|
||||||
|
0xDFF, 0xAB4, 0x55A, 0x2AD, 0xD1D, 0xAC5, 0x929, 0x8DF, 0x824, 0x412, 0x209, 0xD4F, 0xAEC, 0x576, 0x2BB,
|
||||||
|
0xD16, 0x68B, 0xF0E, 0x787, 0xF88, 0x7C4, 0x3E2, 0x1F1, 0xCB3, 0xA12, 0x509, 0xECF, 0xB2C, 0x596, 0x2CB,
|
||||||
|
0xD2E, 0x697, 0xF00, 0x780, 0x3C0, 0x1E0, 0xF0, 0x78, 0x3C, 0x1E, 0xF, 0xC4C, 0x626, 0x313, 0xDC2, 0x6E1,
|
||||||
|
0xF3B, 0xBD6, 0x5EB, 0xEBE, 0x75F, 0xFE4, 0x7F2, 0x3F9, 0xDB7, 0xA90, 0x548, 0x2A4, 0x152, 0xA9, 0xC1F,
|
||||||
|
0xA44, 0x522, 0x291, 0xD03, 0xACA, 0x565, 0xEF9, 0xB37, 0x9D0, 0x4E8, 0x274, 0x13A, 0x9D, 0xC05, 0xA49,
|
||||||
|
0x96F, 0x8FC, 0x47E, 0x23F, 0xD54, 0x6AA, 0x355, 0xDE1, 0xABB, 0x916, 0x48B, 0xE0E, 0x707, 0xFC8, 0x7E4,
|
||||||
|
0x3F2, 0x1F9, 0xCB7, 0xA10, 0x508, 0x284, 0x142, 0xA1, 0xC1B, 0xA46, 0x523, 0xEDA, 0x76D, 0xFFD, 0xBB5,
|
||||||
|
0x991, 0x883, 0x80A, 0x405, 0xE49, 0xB6F, 0x9FC, 0x4FE, 0x27F, 0xD74, 0x6BA, 0x35D, 0xDE5, 0xAB9, 0x917,
|
||||||
|
0x8C0, 0x460, 0x230, 0x118, 0x8C, 0x46, 0x23, 0xC5A, 0x62D, 0xF5D, 0xBE5, 0x9B9, 0x897};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRC-12 checksums for P25 Phase 2 MAC PDU Contents. This is used for a 168-bit message and 12-bits of checksum
|
||||||
|
* using a polynomial of 0x1897 and generated by:
|
||||||
|
* <p>
|
||||||
|
* long[] checksums = CRCUtil.generate(168, 12, 0x1897l, 0x0l, false);
|
||||||
|
*/
|
||||||
|
private static int[] CRC_12_SACCH = new int[]{0x47B, 0xE76, 0x73B, 0xFD6, 0x7EB, 0xFBE, 0x7DF, 0xFA4, 0x7D2, 0x3E9,
|
||||||
|
0xDBF, 0xA94, 0x54A, 0x2A5, 0xD19, 0xAC7, 0x928, 0x494, 0x24A, 0x125, 0xCD9, 0xA27, 0x958, 0x4AC, 0x256,
|
||||||
|
0x12B, 0xCDE, 0x66F, 0xF7C, 0x7BE, 0x3DF, 0xDA4, 0x6D2, 0x369, 0xDFF, 0xAB4, 0x55A, 0x2AD, 0xD1D, 0xAC5,
|
||||||
|
0x929, 0x8DF, 0x824, 0x412, 0x209, 0xD4F, 0xAEC, 0x576, 0x2BB, 0xD16, 0x68B, 0xF0E, 0x787, 0xF88, 0x7C4,
|
||||||
|
0x3E2, 0x1F1, 0xCB3, 0xA12, 0x509, 0xECF, 0xB2C, 0x596, 0x2CB, 0xD2E, 0x697, 0xF00, 0x780, 0x3C0, 0x1E0,
|
||||||
|
0xF0, 0x78, 0x3C, 0x1E, 0xF, 0xC4C, 0x626, 0x313, 0xDC2, 0x6E1, 0xF3B, 0xBD6, 0x5EB, 0xEBE, 0x75F, 0xFE4,
|
||||||
|
0x7F2, 0x3F9, 0xDB7, 0xA90, 0x548, 0x2A4, 0x152, 0xA9, 0xC1F, 0xA44, 0x522, 0x291, 0xD03, 0xACA, 0x565,
|
||||||
|
0xEF9, 0xB37, 0x9D0, 0x4E8, 0x274, 0x13A, 0x9D, 0xC05, 0xA49, 0x96F, 0x8FC, 0x47E, 0x23F, 0xD54, 0x6AA,
|
||||||
|
0x355, 0xDE1, 0xABB, 0x916, 0x48B, 0xE0E, 0x707, 0xFC8, 0x7E4, 0x3F2, 0x1F9, 0xCB7, 0xA10, 0x508, 0x284,
|
||||||
|
0x142, 0xA1, 0xC1B, 0xA46, 0x523, 0xEDA, 0x76D, 0xFFD, 0xBB5, 0x991, 0x883, 0x80A, 0x405, 0xE49, 0xB6F,
|
||||||
|
0x9FC, 0x4FE, 0x27F, 0xD74, 0x6BA, 0x35D, 0xDE5, 0xAB9, 0x917, 0x8C0, 0x460, 0x230, 0x118, 0x8C, 0x46, 0x23,
|
||||||
|
0xC5A, 0x62D, 0xF5D, 0xBE5, 0x9B9, 0x897,};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRC-16 checksums for P25 Phase 2 LCCH MAC PDU Contents. This is used for a 164-bit message and 16-bits of
|
||||||
|
* checksum using a polynomial of 0x11021 and generated by:
|
||||||
|
* <p>
|
||||||
|
* long[] checksums = CRCUtil.generate(164, 16, 0x11021l, 0x0l, true);
|
||||||
|
*/
|
||||||
|
private static int[] CRC_16_LCCH = new int[]{0xCF76, 0x67BB, 0xBBCD, 0xD5F6, 0x6AFB, 0xBD6D, 0xD6A6, 0x6B53, 0xBDB9,
|
||||||
|
0xD6CC, 0x6B66, 0x35B3, 0x92C9, 0xC174, 0x60BA, 0x305D, 0x903E, 0x481F, 0xAC1F, 0xDE1F, 0xE71F, 0xFB9F,
|
||||||
|
0xF5DF, 0xF2FF, 0xF16F, 0xF0A7, 0xF043, 0xF031, 0xF008, 0x7804, 0x3C02, 0x1E01, 0x8710, 0x4388, 0x21C4,
|
||||||
|
0x10E2, 0x871, 0x8C28, 0x4614, 0x230A, 0x1185, 0x80D2, 0x4069, 0xA824, 0x5412, 0x2A09, 0x9D14, 0x4E8A,
|
||||||
|
0x2745, 0x9BB2, 0x4DD9, 0xAEFC, 0x577E, 0x2BBF, 0x9DCF, 0xC6F7, 0xEB6B, 0xFDA5, 0xF6C2, 0x7B61, 0xB5A0,
|
||||||
|
0x5AD0, 0x2D68, 0x16B4, 0xB5A, 0x5AD, 0x8AC6, 0x4563, 0xAAA1, 0xDD40, 0x6EA0, 0x3750, 0x1BA8, 0xDD4, 0x6EA,
|
||||||
|
0x375, 0x89AA, 0x44D5, 0xAA7A, 0x553D, 0xA28E, 0x5147, 0xA0B3, 0xD849, 0xE434, 0x721A, 0x390D, 0x9496,
|
||||||
|
0x4A4B, 0xAD35, 0xDE8A, 0x6F45, 0xBFB2, 0x5FD9, 0xA7FC, 0x53FE, 0x29FF, 0x9CEF, 0xC667, 0xEB23, 0xFD81,
|
||||||
|
0xF6D0, 0x7B68, 0x3DB4, 0x1EDA, 0xF6D, 0x8FA6, 0x47D3, 0xABF9, 0xDDEC, 0x6EF6, 0x377B, 0x93AD, 0xC1C6,
|
||||||
|
0x60E3, 0xB861, 0xD420, 0x6A10, 0x3508, 0x1A84, 0xD42, 0x6A1, 0x8B40, 0x45A0, 0x22D0, 0x1168, 0x8B4, 0x45A,
|
||||||
|
0x22D, 0x8906, 0x4483, 0xAA51, 0xDD38, 0x6E9C, 0x374E, 0x1BA7, 0x85C3, 0xCAF1, 0xED68, 0x76B4, 0x3B5A,
|
||||||
|
0x1DAD, 0x86C6, 0x4363, 0xA9A1, 0xDCC0, 0x6E60, 0x3730, 0x1B98, 0xDCC, 0x6E6, 0x373, 0x89A9, 0xCCC4, 0x6662,
|
||||||
|
0x3331, 0x9188, 0x48C4, 0x2462, 0x1231, 0x8108, 0x4084, 0x2042, 0x1021, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20,
|
||||||
|
0x40, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs error detection and single-bit error correction against the
|
* Performs error detection and single-bit error correction against the
|
||||||
* data blocks of a PDU1 message.
|
* data blocks of a PDU1 message.
|
||||||
@@ -402,11 +463,8 @@ public class CRCP25
|
|||||||
}
|
}
|
||||||
|
|
||||||
int checksum = message.getInt(messageStart + 7, messageStart + 15);
|
int checksum = message.getInt(messageStart + 7, messageStart + 15);
|
||||||
|
|
||||||
int residual = calculated ^ checksum;
|
int residual = calculated ^ checksum;
|
||||||
|
|
||||||
// mLog.debug( "CALC:" + calculated + " CHECK:" + checksum + " RESID:" + residual );
|
|
||||||
|
|
||||||
if(residual == 0 || residual == 0x1FF)
|
if(residual == 0 || residual == 0x1FF)
|
||||||
{
|
{
|
||||||
return CRC.PASSED;
|
return CRC.PASSED;
|
||||||
@@ -441,12 +499,80 @@ public class CRCP25
|
|||||||
return passes;
|
return passes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the CRC-12 checksum for the P25 Phase 2 MAC PDU Contents for S-OEMI/FACCH and compares it to the
|
||||||
|
* transmitted checksum to verify that the message is correct.
|
||||||
|
* Note: this uses the pre-calculated checksums in the CRC_12_FACCH array.
|
||||||
|
*
|
||||||
|
* @param message to verify
|
||||||
|
* @return true if the message passes the CRC-12 check.
|
||||||
|
*/
|
||||||
|
public static boolean crc12_FACCH(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
int calculated = 0xFFF; //Initial fill of all ones.
|
||||||
|
|
||||||
|
//Iterate the set bits and XOR the running checksum with pre-calculated lookup value
|
||||||
|
for(int i = message.nextSetBit(0); i >= 0 && i < 144; i = message.nextSetBit(i + 1))
|
||||||
|
{
|
||||||
|
calculated ^= CRC_12_FACCH[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int checksum = message.getInt(144, 155); //12-bit transmitted checksum
|
||||||
|
int residual = calculated ^ checksum;
|
||||||
|
return residual == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the CRC-12 checksum for the P25 Phase 2 MAC PDU Contents for I-OEMI/SACCH and compares it to the
|
||||||
|
* transmitted checksum to verify that the message is correct.
|
||||||
|
* Note: this uses the pre-calculated checksums in the CRC_12_SACCH array.
|
||||||
|
*
|
||||||
|
* @param message to verify
|
||||||
|
* @return true if the message passes the CRC-12 check.
|
||||||
|
*/
|
||||||
|
public static boolean crc12_SACCH(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
int calculated = 0xFFF; //Initial fill of all ones.
|
||||||
|
|
||||||
|
//Iterate the set bits and XOR the running checksum with pre-calculated lookup value
|
||||||
|
for(int i = message.nextSetBit(0); i >= 0 && i < 168; i = message.nextSetBit(i + 1))
|
||||||
|
{
|
||||||
|
calculated ^= CRC_12_SACCH[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int checksum = message.getInt(168, 179); //12-bit transmitted checksum
|
||||||
|
int residual = calculated ^ checksum;
|
||||||
|
return residual == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the CRC-16 checksum for the P25 Phase 2 MAC PDU Contents for I-OECI/LCCH and compares it to the
|
||||||
|
* transmitted checksum to verify that the message is correct.
|
||||||
|
* Note: this uses the pre-calculated checksums in the CRC_16_LCCH array.
|
||||||
|
*
|
||||||
|
* @param message to verify
|
||||||
|
* @return true if the message passes the CRC-16 check.
|
||||||
|
*/
|
||||||
|
public static boolean crc16_LCCH(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
int calculated = 0xFFFF; //Initial fill of all ones.
|
||||||
|
|
||||||
|
//Iterate the set bits and XOR the running checksum with pre-calculated lookup value
|
||||||
|
for(int i = message.nextSetBit(0); i >= 0 && i < 164; i = message.nextSetBit(i + 1))
|
||||||
|
{
|
||||||
|
calculated ^= CRC_16_LCCH[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int checksum = message.getInt(164, 179); //16-bit transmitted checksum
|
||||||
|
int residual = calculated ^ checksum;
|
||||||
|
return residual == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the value of the message checksum as a long
|
* Calculates the value of the message checksum as a long
|
||||||
*/
|
*/
|
||||||
public static long getLongChecksum(BinaryMessage message,
|
public static long getLongChecksum(BinaryMessage message, int crcStart, int crcLength)
|
||||||
int crcStart, int crcLength)
|
|
||||||
{
|
{
|
||||||
return message.getLong(crcStart, crcStart + crcLength - 1);
|
return message.getLong(crcStart, crcStart + crcLength - 1);
|
||||||
}
|
}
|
||||||
@@ -454,8 +580,7 @@ public class CRCP25
|
|||||||
/**
|
/**
|
||||||
* Calculates the value of the message checksum as an integer
|
* Calculates the value of the message checksum as an integer
|
||||||
*/
|
*/
|
||||||
public static int getIntChecksum(BinaryMessage message,
|
public static int getIntChecksum(BinaryMessage message, int crcStart, int crcLength)
|
||||||
int crcStart, int crcLength)
|
|
||||||
{
|
{
|
||||||
return message.getInt(crcStart, crcStart + crcLength - 1);
|
return message.getInt(crcStart, crcStart + crcLength - 1);
|
||||||
}
|
}
|
||||||
@@ -494,16 +619,19 @@ public class CRCP25
|
|||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
String raw = "000000001000001100000001010001111011000100001010010001111100000000000101000000000000000001000000000000110000000000000001101010101010101010101010";
|
long[] checksums = CRCUtil.generate(144, 12, 0x1897l, 0x0l, false);
|
||||||
|
System.out.println("Checksums:" + Arrays.toString(checksums));
|
||||||
|
|
||||||
BinaryMessage message = BinaryMessage.load(raw);
|
String hex12 = "7C0000000000000000000000000000000000CA7";
|
||||||
|
|
||||||
mLog.debug("MSG:" + message.toString());
|
// BinaryMessage message = BinaryMessage.load(raw);
|
||||||
|
//
|
||||||
CRC results = checkCRC9(message, 0);
|
// mLog.debug("MSG:" + message.toString());
|
||||||
|
//
|
||||||
mLog.debug("COR:" + message.toString());
|
// CRC results = checkCRC9(message, 0);
|
||||||
|
//
|
||||||
mLog.debug("Results: " + results.getDisplayText());
|
// mLog.debug("COR:" + message.toString());
|
||||||
|
//
|
||||||
|
// mLog.debug("Results: " + results.getDisplayText());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -32,8 +33,7 @@ public class CRCUtil
|
|||||||
long initialFill,
|
long initialFill,
|
||||||
boolean includeCRCBitErrors)
|
boolean includeCRCBitErrors)
|
||||||
{
|
{
|
||||||
long[] crcTable = new long[messageSize +
|
long[] crcTable = new long[messageSize + (includeCRCBitErrors ? crcSize : 0)];
|
||||||
(includeCRCBitErrors ? crcSize : 0)];
|
|
||||||
|
|
||||||
int[] checksumIndexes = new int[crcSize];
|
int[] checksumIndexes = new int[crcSize];
|
||||||
|
|
||||||
@@ -132,20 +132,12 @@ public class CRCUtil
|
|||||||
* @return message with all message bits zeroed out, and the remainder
|
* @return message with all message bits zeroed out, and the remainder
|
||||||
* placed in the crc field which starts at index messageLength
|
* placed in the crc field which starts at index messageLength
|
||||||
*/
|
*/
|
||||||
public static BinaryMessage decode(BinaryMessage message,
|
public static BinaryMessage decode(BinaryMessage message, int messageStart, int messageSize, long polynomial, int crcSize)
|
||||||
int messageStart,
|
|
||||||
int messageSize,
|
|
||||||
long polynomial,
|
|
||||||
int crcSize)
|
|
||||||
{
|
{
|
||||||
for(int i = message.nextSetBit(messageStart);
|
for(int i = message.nextSetBit(messageStart); i >= messageStart && i < messageSize; i = message.nextSetBit(i + 1))
|
||||||
i >= messageStart && i < messageSize;
|
|
||||||
i = message.nextSetBit(i + 1))
|
|
||||||
{
|
{
|
||||||
BinaryMessage polySet = new BinaryMessage(crcSize + i + 1);
|
BinaryMessage polySet = new BinaryMessage(crcSize + i + 1);
|
||||||
|
|
||||||
polySet.load(i, crcSize + 1, polynomial);
|
polySet.load(i, crcSize + 1, polynomial);
|
||||||
|
|
||||||
message.xor(polySet);
|
message.xor(polySet);
|
||||||
System.out.println(message.toString());
|
System.out.println(message.toString());
|
||||||
}
|
}
|
||||||
@@ -226,8 +218,11 @@ public class CRCUtil
|
|||||||
{
|
{
|
||||||
mLog.debug("Starting");
|
mLog.debug("Starting");
|
||||||
|
|
||||||
long poly = 0x13l;
|
int messageSize = 164;
|
||||||
long[] checksums = generate(32, 4, poly, 0, true);
|
int crcSize = 16;
|
||||||
|
long poly = 0x11021l;
|
||||||
|
long initialFill = 0x0l;
|
||||||
|
long[] checksums = CRCUtil.generate(messageSize, crcSize, poly, initialFill, true);
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("private static int[] CHECKSUMS = new int[]{");
|
sb.append("private static int[] CHECKSUMS = new int[]{");
|
||||||
@@ -238,7 +233,29 @@ public class CRCUtil
|
|||||||
}
|
}
|
||||||
|
|
||||||
sb.append("};");
|
sb.append("};");
|
||||||
|
|
||||||
System.out.println("Checksums:\n" + sb);
|
System.out.println("Checksums:\n" + sb);
|
||||||
|
|
||||||
|
String hex = "7CFC0039420170452A4F9970000000000000000000D05";
|
||||||
|
CorrectedBinaryMessage message = new CorrectedBinaryMessage(BinaryMessage.loadHex(hex));
|
||||||
|
|
||||||
|
|
||||||
|
int calculated = 0xFFF; //Initial fill of all ones
|
||||||
|
|
||||||
|
int messageStart = 0;
|
||||||
|
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||||
|
for(int i = message.nextSetBit(messageStart);
|
||||||
|
i >= messageStart && i < messageSize;
|
||||||
|
i = message.nextSetBit(i + 1))
|
||||||
|
{
|
||||||
|
System.out.println("Bit [" + i + "] is set");
|
||||||
|
calculated ^= checksums[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int checksum = message.getInt(messageSize, messageSize + crcSize - 1);
|
||||||
|
int residual = calculated ^ checksum;
|
||||||
|
|
||||||
|
mLog.debug("CALC:" + Integer.toHexString(calculated).toUpperCase() +
|
||||||
|
" CHECK:" + Integer.toHexString(checksum).toUpperCase() +
|
||||||
|
" RESIDUAL:" + Integer.toHexString(residual).toUpperCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,7 +35,7 @@ import io.github.dsheirer.gui.preference.PreferenceEditorType;
|
|||||||
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
|
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
|
||||||
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
||||||
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
||||||
import io.github.dsheirer.gui.viewer.RecordingViewer;
|
import io.github.dsheirer.gui.viewer.MessageRecordingViewer;
|
||||||
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
|
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.jmbe.JmbeEditor;
|
import io.github.dsheirer.jmbe.JmbeEditor;
|
||||||
@@ -84,7 +84,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
private TunerManager mTunerManager;
|
private TunerManager mTunerManager;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private UserPreferencesEditor mUserPreferencesEditor;
|
private UserPreferencesEditor mUserPreferencesEditor;
|
||||||
private RecordingViewer mRecordingViewer;
|
private MessageRecordingViewer mMessageRecordingViewer;
|
||||||
|
|
||||||
private Stage mChannelMapStage;
|
private Stage mChannelMapStage;
|
||||||
private Stage mIconManagerStage;
|
private Stage mIconManagerStage;
|
||||||
@@ -216,14 +216,14 @@ public class JavaFxWindowManager extends Application
|
|||||||
return mRecordingViewerStage;
|
return mRecordingViewerStage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecordingViewer getRecordingViewer()
|
public MessageRecordingViewer getRecordingViewer()
|
||||||
{
|
{
|
||||||
if(mRecordingViewer == null)
|
if(mMessageRecordingViewer == null)
|
||||||
{
|
{
|
||||||
mRecordingViewer = new RecordingViewer();
|
mMessageRecordingViewer = new MessageRecordingViewer();
|
||||||
}
|
}
|
||||||
|
|
||||||
return mRecordingViewer;
|
return mMessageRecordingViewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stage getIconManagerStage()
|
public Stage getIconManagerStage()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -647,11 +647,13 @@ public class ChannelEditor extends SplitPane implements IFilterProcessor
|
|||||||
public NewP25P2ChannelMenu()
|
public NewP25P2ChannelMenu()
|
||||||
{
|
{
|
||||||
setText(DecoderType.P25_PHASE2.getDisplayString());
|
setText(DecoderType.P25_PHASE2.getDisplayString());
|
||||||
MenuItem trunked = new MenuItem("Trunked System");
|
MenuItem trunkedP1 = new MenuItem("Trunked System - FDMA Phase 1 Control Channel");
|
||||||
trunked.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE1));
|
trunkedP1.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE1));
|
||||||
MenuItem channel = new MenuItem("Individual Channel");
|
MenuItem trunkedP2 = new MenuItem("Trunked System - TDMA Phase 2 Control Channel");
|
||||||
|
trunkedP2.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE2));
|
||||||
|
MenuItem channel = new MenuItem("Individual Phase 2 Channel");
|
||||||
channel.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE2));
|
channel.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE2));
|
||||||
getItems().addAll(trunked, channel);
|
getItems().addAll(trunkedP1, trunkedP2, channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -113,7 +113,7 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
if(mDecoderPane == null)
|
if(mDecoderPane == null)
|
||||||
{
|
{
|
||||||
mDecoderPane = new TitledPane();
|
mDecoderPane = new TitledPane();
|
||||||
mDecoderPane.setText("Decoder: P25 Phase 1 (or P25 Phase 2 Control Channel)");
|
mDecoderPane.setText("Decoder: P25 Phase 1 (also used for P25 Phase 2 system with FDMA control channels)");
|
||||||
mDecoderPane.setExpanded(true);
|
mDecoderPane.setExpanded(true);
|
||||||
|
|
||||||
GridPane gridPane = new GridPane();
|
GridPane gridPane = new GridPane();
|
||||||
@@ -355,9 +355,9 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
{
|
{
|
||||||
DecodeConfigP25Phase1 config;
|
DecodeConfigP25Phase1 config;
|
||||||
|
|
||||||
if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase1)
|
if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase1 p1)
|
||||||
{
|
{
|
||||||
config = (DecodeConfigP25Phase1)getItem().getDecodeConfiguration();
|
config = p1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,9 +42,13 @@ import java.util.List;
|
|||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Spinner;
|
||||||
|
import javafx.scene.control.SpinnerValueFactory;
|
||||||
import javafx.scene.control.TitledPane;
|
import javafx.scene.control.TitledPane;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.controlsfx.control.ToggleSwitch;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -64,6 +68,8 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private IntegerTextField mWacnTextField;
|
private IntegerTextField mWacnTextField;
|
||||||
private IntegerTextField mSystemTextField;
|
private IntegerTextField mSystemTextField;
|
||||||
private IntegerTextField mNacTextField;
|
private IntegerTextField mNacTextField;
|
||||||
|
private ToggleSwitch mIgnoreDataCallsButton;
|
||||||
|
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -102,7 +108,7 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
if(mDecoderPane == null)
|
if(mDecoderPane == null)
|
||||||
{
|
{
|
||||||
mDecoderPane = new TitledPane();
|
mDecoderPane = new TitledPane();
|
||||||
mDecoderPane.setText("Decoder: P25 Phase 2");
|
mDecoderPane.setText("Decoder: P25 Phase 2 for TDMA control or TDMA traffic channels.");
|
||||||
mDecoderPane.setExpanded(true);
|
mDecoderPane.setExpanded(true);
|
||||||
|
|
||||||
GridPane gridPane = new GridPane();
|
GridPane gridPane = new GridPane();
|
||||||
@@ -112,6 +118,22 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
|
|
||||||
int row = 0;
|
int row = 0;
|
||||||
|
|
||||||
|
Label poolSizeLabel = new Label("Max Traffic Channels");
|
||||||
|
GridPane.setHalignment(poolSizeLabel, HPos.RIGHT);
|
||||||
|
GridPane.setConstraints(poolSizeLabel, 0, row);
|
||||||
|
gridPane.getChildren().add(poolSizeLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getTrafficChannelPoolSizeSpinner(), 1, row);
|
||||||
|
gridPane.getChildren().add(getTrafficChannelPoolSizeSpinner());
|
||||||
|
|
||||||
|
GridPane.setConstraints(getIgnoreDataCallsButton(), 2, row);
|
||||||
|
gridPane.getChildren().add(getIgnoreDataCallsButton());
|
||||||
|
|
||||||
|
Label directionLabel = new Label("Ignore Data Calls");
|
||||||
|
GridPane.setHalignment(directionLabel, HPos.LEFT);
|
||||||
|
GridPane.setConstraints(directionLabel, 3, row);
|
||||||
|
gridPane.getChildren().add(directionLabel);
|
||||||
|
|
||||||
Label wacnLabel = new Label("WACN");
|
Label wacnLabel = new Label("WACN");
|
||||||
GridPane.setHalignment(wacnLabel, HPos.RIGHT);
|
GridPane.setHalignment(wacnLabel, HPos.RIGHT);
|
||||||
GridPane.setConstraints(wacnLabel, 0, ++row);
|
GridPane.setConstraints(wacnLabel, 0, ++row);
|
||||||
@@ -122,20 +144,27 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
|
|
||||||
Label systemLabel = new Label("System");
|
Label systemLabel = new Label("System");
|
||||||
GridPane.setHalignment(systemLabel, HPos.RIGHT);
|
GridPane.setHalignment(systemLabel, HPos.RIGHT);
|
||||||
GridPane.setConstraints(systemLabel, 0, ++row);
|
GridPane.setConstraints(systemLabel, 2, row);
|
||||||
gridPane.getChildren().add(systemLabel);
|
gridPane.getChildren().add(systemLabel);
|
||||||
|
|
||||||
GridPane.setConstraints(getSystemTextField(), 1, row);
|
GridPane.setConstraints(getSystemTextField(), 3, row);
|
||||||
gridPane.getChildren().add(getSystemTextField());
|
gridPane.getChildren().add(getSystemTextField());
|
||||||
|
|
||||||
Label nacLabel = new Label("NAC");
|
Label nacLabel = new Label("NAC");
|
||||||
GridPane.setHalignment(nacLabel, HPos.RIGHT);
|
GridPane.setHalignment(nacLabel, HPos.RIGHT);
|
||||||
GridPane.setConstraints(nacLabel, 0, ++row);
|
GridPane.setConstraints(nacLabel, 4, row);
|
||||||
gridPane.getChildren().add(nacLabel);
|
gridPane.getChildren().add(nacLabel);
|
||||||
|
|
||||||
GridPane.setConstraints(getNacTextField(), 1, row);
|
GridPane.setConstraints(getNacTextField(), 5, row);
|
||||||
gridPane.getChildren().add(getNacTextField());
|
gridPane.getChildren().add(getNacTextField());
|
||||||
|
|
||||||
|
Label noteLabel = new Label("Note: WACN/System/NAC values are auto-detected (ie not required) from " +
|
||||||
|
"the control channel and are only required when decoding individual traffic channels");
|
||||||
|
GridPane.setHalignment(noteLabel, HPos.LEFT);
|
||||||
|
GridPane.setConstraints(noteLabel, 1, ++row, 6, 1);
|
||||||
|
gridPane.getChildren().add(noteLabel);
|
||||||
|
|
||||||
|
|
||||||
mDecoderPane.setContent(gridPane);
|
mDecoderPane.setContent(gridPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +232,37 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
return mEventLogConfigurationEditor;
|
return mEventLogConfigurationEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ToggleSwitch getIgnoreDataCallsButton()
|
||||||
|
{
|
||||||
|
if(mIgnoreDataCallsButton == null)
|
||||||
|
{
|
||||||
|
mIgnoreDataCallsButton = new ToggleSwitch();
|
||||||
|
mIgnoreDataCallsButton.setDisable(true);
|
||||||
|
mIgnoreDataCallsButton.selectedProperty()
|
||||||
|
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIgnoreDataCallsButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
|
||||||
|
{
|
||||||
|
if(mTrafficChannelPoolSizeSpinner == null)
|
||||||
|
{
|
||||||
|
mTrafficChannelPoolSizeSpinner = new Spinner();
|
||||||
|
mTrafficChannelPoolSizeSpinner.setDisable(true);
|
||||||
|
mTrafficChannelPoolSizeSpinner.setTooltip(
|
||||||
|
new Tooltip("Maximum number of traffic channels that can be created by the decoder"));
|
||||||
|
mTrafficChannelPoolSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||||
|
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 50);
|
||||||
|
mTrafficChannelPoolSizeSpinner.setValueFactory(svf);
|
||||||
|
mTrafficChannelPoolSizeSpinner.getValueFactory().valueProperty()
|
||||||
|
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTrafficChannelPoolSizeSpinner;
|
||||||
|
}
|
||||||
|
|
||||||
private IntegerTextField getWacnTextField()
|
private IntegerTextField getWacnTextField()
|
||||||
{
|
{
|
||||||
if(mWacnTextField == null)
|
if(mWacnTextField == null)
|
||||||
@@ -247,6 +307,9 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
types.add(RecorderType.BASEBAND);
|
types.add(RecorderType.BASEBAND);
|
||||||
types.add(RecorderType.DEMODULATED_BIT_STREAM);
|
types.add(RecorderType.DEMODULATED_BIT_STREAM);
|
||||||
types.add(RecorderType.MBE_CALL_SEQUENCE);
|
types.add(RecorderType.MBE_CALL_SEQUENCE);
|
||||||
|
types.add(RecorderType.TRAFFIC_BASEBAND);
|
||||||
|
types.add(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM);
|
||||||
|
types.add(RecorderType.TRAFFIC_MBE_CALL_SEQUENCE);
|
||||||
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
|
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
|
||||||
mRecordConfigurationEditor.setDisable(true);
|
mRecordConfigurationEditor.setDisable(true);
|
||||||
mRecordConfigurationEditor.modifiedProperty()
|
mRecordConfigurationEditor.modifiedProperty()
|
||||||
@@ -259,9 +322,8 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
@Override
|
@Override
|
||||||
protected void setDecoderConfiguration(DecodeConfiguration config)
|
protected void setDecoderConfiguration(DecodeConfiguration config)
|
||||||
{
|
{
|
||||||
if(config instanceof DecodeConfigP25Phase2)
|
if(config instanceof DecodeConfigP25Phase2 decodeConfig)
|
||||||
{
|
{
|
||||||
DecodeConfigP25Phase2 decodeConfig = (DecodeConfigP25Phase2)config;
|
|
||||||
getWacnTextField().setDisable(false);
|
getWacnTextField().setDisable(false);
|
||||||
getSystemTextField().setDisable(false);
|
getSystemTextField().setDisable(false);
|
||||||
getNacTextField().setDisable(false);
|
getNacTextField().setDisable(false);
|
||||||
@@ -280,6 +342,11 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
getSystemTextField().set(0);
|
getSystemTextField().set(0);
|
||||||
getNacTextField().set(0);
|
getNacTextField().set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIgnoreDataCallsButton().setDisable(false);
|
||||||
|
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
||||||
|
getTrafficChannelPoolSizeSpinner().setDisable(false);
|
||||||
|
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -289,6 +356,8 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
getWacnTextField().setDisable(true);
|
getWacnTextField().setDisable(true);
|
||||||
getSystemTextField().setDisable(true);
|
getSystemTextField().setDisable(true);
|
||||||
getNacTextField().setDisable(true);
|
getNacTextField().setDisable(true);
|
||||||
|
getIgnoreDataCallsButton().setDisable(true);
|
||||||
|
getTrafficChannelPoolSizeSpinner().setDisable(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,20 +366,23 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
{
|
{
|
||||||
DecodeConfigP25Phase2 config;
|
DecodeConfigP25Phase2 config;
|
||||||
|
|
||||||
if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase2)
|
if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase2 p2)
|
||||||
{
|
{
|
||||||
config = (DecodeConfigP25Phase2)getItem().getDecodeConfiguration();
|
config = p2;
|
||||||
config.setAutoDetectScrambleParameters(false);
|
|
||||||
int wacn = getWacnTextField().get();
|
|
||||||
int system = getSystemTextField().get();
|
|
||||||
int nac = getNacTextField().get();
|
|
||||||
config.setScrambleParameters(new ScrambleParameters(wacn, system, nac));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
config = new DecodeConfigP25Phase2();
|
config = new DecodeConfigP25Phase2();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.setAutoDetectScrambleParameters(false);
|
||||||
|
int wacn = getWacnTextField().get();
|
||||||
|
int system = getSystemTextField().get();
|
||||||
|
int nac = getNacTextField().get();
|
||||||
|
config.setScrambleParameters(new ScrambleParameters(wacn, system, nac));
|
||||||
|
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
||||||
|
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
||||||
|
|
||||||
getItem().setDecodeConfiguration(config);
|
getItem().setDecodeConfiguration(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,13 +25,15 @@ import io.github.dsheirer.rrapi.type.Site;
|
|||||||
/**
|
/**
|
||||||
* Wrapper class to join a Site and a corresponding County Info
|
* Wrapper class to join a Site and a corresponding County Info
|
||||||
*/
|
*/
|
||||||
public class EnrichedSite
|
public class EnrichedSite implements Comparable<EnrichedSite>
|
||||||
{
|
{
|
||||||
|
private static final String PHASE_2_TDMA_MODULATION = "TDMA";
|
||||||
private Site mSite;
|
private Site mSite;
|
||||||
private CountyInfo mCountyInfo;
|
private CountyInfo mCountyInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
|
*
|
||||||
* @param site object
|
* @param site object
|
||||||
* @param countyInfo optional
|
* @param countyInfo optional
|
||||||
*/
|
*/
|
||||||
@@ -41,8 +43,16 @@ public class EnrichedSite
|
|||||||
mCountyInfo = countyInfo;
|
mCountyInfo = countyInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String format(int value)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(value).append(" (").append(Integer.toHexString(value).toUpperCase()).append(")");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Site instance
|
* Site instance
|
||||||
|
*
|
||||||
* @return site or null
|
* @return site or null
|
||||||
*/
|
*/
|
||||||
public Site getSite()
|
public Site getSite()
|
||||||
@@ -52,6 +62,7 @@ public class EnrichedSite
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the site instance
|
* Sets the site instance
|
||||||
|
*
|
||||||
* @param site or null
|
* @param site or null
|
||||||
*/
|
*/
|
||||||
public void setSite(Site site)
|
public void setSite(Site site)
|
||||||
@@ -61,6 +72,7 @@ public class EnrichedSite
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* County information
|
* County information
|
||||||
|
*
|
||||||
* @return county info or null
|
* @return county info or null
|
||||||
*/
|
*/
|
||||||
public CountyInfo getCountyInfo()
|
public CountyInfo getCountyInfo()
|
||||||
@@ -70,6 +82,7 @@ public class EnrichedSite
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the county info
|
* Sets the county info
|
||||||
|
*
|
||||||
* @param countyInfo or null
|
* @param countyInfo or null
|
||||||
*/
|
*/
|
||||||
public void setCountyInfo(CountyInfo countyInfo)
|
public void setCountyInfo(CountyInfo countyInfo)
|
||||||
@@ -78,13 +91,26 @@ public class EnrichedSite
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional site number
|
* Formatted system identity
|
||||||
|
*
|
||||||
|
* @return
|
||||||
*/
|
*/
|
||||||
public Integer getSiteNumber()
|
public String getSystemFormatted()
|
||||||
{
|
{
|
||||||
if(mSite != null)
|
if(mSite != null)
|
||||||
{
|
{
|
||||||
return mSite.getSiteNumber();
|
//System number is stored in the zone number field.
|
||||||
|
return format(mSite.getZoneNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSiteFormatted()
|
||||||
|
{
|
||||||
|
if(mSite != null)
|
||||||
|
{
|
||||||
|
return format(mSite.getSiteNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -93,11 +119,11 @@ public class EnrichedSite
|
|||||||
/**
|
/**
|
||||||
* Optional site RFSS value
|
* Optional site RFSS value
|
||||||
*/
|
*/
|
||||||
public Integer getRfss()
|
public String getRfssFormatted()
|
||||||
{
|
{
|
||||||
if(mSite != null)
|
if(mSite != null)
|
||||||
{
|
{
|
||||||
return mSite.getRfss();
|
return format(mSite.getRfss());
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -128,4 +154,23 @@ public class EnrichedSite
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String system = getSystemFormatted();
|
||||||
|
sb.append(system == null ? "-" : system).append(" ");
|
||||||
|
String rfss = getRfssFormatted();
|
||||||
|
sb.append(rfss == null ? "-" : rfss).append(" ");
|
||||||
|
String site = getSiteFormatted();
|
||||||
|
sb.append(site == null ? "-" : site);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(EnrichedSite o)
|
||||||
|
{
|
||||||
|
return this.toString().compareTo(o.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -449,6 +449,16 @@ public class RadioReferenceDecoder
|
|||||||
* @return decoder type or null.
|
* @return decoder type or null.
|
||||||
*/
|
*/
|
||||||
public DecoderType getDecoderType(System system)
|
public DecoderType getDecoderType(System system)
|
||||||
|
{
|
||||||
|
return getDecoderType(system, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder type for the specified system, if supported.
|
||||||
|
* @param system requiring a decoder type
|
||||||
|
* @return decoder type or null.
|
||||||
|
*/
|
||||||
|
public DecoderType getDecoderType(System system, Site site)
|
||||||
{
|
{
|
||||||
Type type = getType(system);
|
Type type = getType(system);
|
||||||
Flavor flavor = getFlavor(system);
|
Flavor flavor = getFlavor(system);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2024 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.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
|||||||
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
|
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
|
||||||
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.rrapi.type.Flavor;
|
||||||
import io.github.dsheirer.rrapi.type.RadioNetwork;
|
import io.github.dsheirer.rrapi.type.RadioNetwork;
|
||||||
import io.github.dsheirer.rrapi.type.Site;
|
import io.github.dsheirer.rrapi.type.Site;
|
||||||
import io.github.dsheirer.rrapi.type.SiteFrequency;
|
import io.github.dsheirer.rrapi.type.SiteFrequency;
|
||||||
@@ -42,6 +43,13 @@ import io.github.dsheirer.rrapi.type.System;
|
|||||||
import io.github.dsheirer.rrapi.type.SystemInformation;
|
import io.github.dsheirer.rrapi.type.SystemInformation;
|
||||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import javafx.animation.RotateTransition;
|
import javafx.animation.RotateTransition;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
@@ -74,14 +82,6 @@ import org.controlsfx.control.SegmentedButton;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class SiteEditor extends GridPane
|
public class SiteEditor extends GridPane
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(SiteEditor.class);
|
private static final Logger mLog = LoggerFactory.getLogger(SiteEditor.class);
|
||||||
@@ -90,6 +90,8 @@ public class SiteEditor extends GridPane
|
|||||||
private static final String PRIMARY_CONTROL_CHANNEL = "d";
|
private static final String PRIMARY_CONTROL_CHANNEL = "d";
|
||||||
private static final String TOGGLE_BUTTON_CONTROL = "Control";
|
private static final String TOGGLE_BUTTON_CONTROL = "Control";
|
||||||
private static final String TOGGLE_BUTTON_P25_VOICE = "All P25 Voice";
|
private static final String TOGGLE_BUTTON_P25_VOICE = "All P25 Voice";
|
||||||
|
private static final String PHASE_2_TDMA_MODULATION = "TDMA";
|
||||||
|
private static final String PHASE_2_FLAVOR = "Phase II";
|
||||||
|
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private PlaylistManager mPlaylistManager;
|
private PlaylistManager mPlaylistManager;
|
||||||
@@ -115,6 +117,10 @@ public class SiteEditor extends GridPane
|
|||||||
private SystemInformation mCurrentSystemInformation;
|
private SystemInformation mCurrentSystemInformation;
|
||||||
private ComboBox mAliasListNameComboBox;
|
private ComboBox mAliasListNameComboBox;
|
||||||
private Button mNewAliasListButton;
|
private Button mNewAliasListButton;
|
||||||
|
private Label mP25ControlLabel;
|
||||||
|
private SegmentedButton mP25ControlSegmentedButton;
|
||||||
|
private ToggleButton mFdmaControlToggleButton;
|
||||||
|
private ToggleButton mTdmaControlToggleButton;
|
||||||
|
|
||||||
public SiteEditor(UserPreferences userPreferences, PlaylistManager playlistManager)
|
public SiteEditor(UserPreferences userPreferences, PlaylistManager playlistManager)
|
||||||
{
|
{
|
||||||
@@ -163,6 +169,13 @@ public class SiteEditor extends GridPane
|
|||||||
GridPane.setConstraints(getConfigurationsSegmentedButton(), 2, row);
|
GridPane.setConstraints(getConfigurationsSegmentedButton(), 2, row);
|
||||||
getChildren().add(getConfigurationsSegmentedButton());
|
getChildren().add(getConfigurationsSegmentedButton());
|
||||||
|
|
||||||
|
GridPane.setConstraints(getP25ControlLabel(), 1, ++row);
|
||||||
|
GridPane.setHalignment(getP25ControlLabel(), HPos.RIGHT);
|
||||||
|
getChildren().add(getP25ControlLabel());
|
||||||
|
|
||||||
|
GridPane.setConstraints(getP25ControlSegmentedButton(), 2, row);
|
||||||
|
getChildren().add(getP25ControlSegmentedButton());
|
||||||
|
|
||||||
Label systemLabel = new Label("System");
|
Label systemLabel = new Label("System");
|
||||||
GridPane.setConstraints(systemLabel, 1, ++row);
|
GridPane.setConstraints(systemLabel, 1, ++row);
|
||||||
GridPane.setHalignment(systemLabel, HPos.RIGHT);
|
GridPane.setHalignment(systemLabel, HPos.RIGHT);
|
||||||
@@ -206,7 +219,7 @@ public class SiteEditor extends GridPane
|
|||||||
createBox.setAlignment(Pos.CENTER_LEFT);
|
createBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
createBox.setSpacing(10);
|
createBox.setSpacing(10);
|
||||||
createBox.getChildren().addAll(getCreateChannelConfigurationButton(), getGoToChannelEditorCheckBox());
|
createBox.getChildren().addAll(getCreateChannelConfigurationButton(), getGoToChannelEditorCheckBox());
|
||||||
GridPane.setConstraints(createBox, 2, ++row + 1);
|
GridPane.setConstraints(createBox, 2, ++row);
|
||||||
getChildren().addAll(createBox);
|
getChildren().addAll(createBox);
|
||||||
|
|
||||||
//Note: the following label node is added to the same location as the buttons and visibility is toggled
|
//Note: the following label node is added to the same location as the buttons and visibility is toggled
|
||||||
@@ -215,6 +228,13 @@ public class SiteEditor extends GridPane
|
|||||||
getChildren().add(getProtocolNotSupportedLabel());
|
getChildren().add(getProtocolNotSupportedLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a channel decode configuration for the specified site.
|
||||||
|
* @param decoderType to create
|
||||||
|
* @param site information and frequencies
|
||||||
|
* @param systemInformation with frequency mapping
|
||||||
|
* @return decode configuration
|
||||||
|
*/
|
||||||
private DecodeConfiguration getDecodeConfiguration(DecoderType decoderType, Site site, SystemInformation systemInformation)
|
private DecodeConfiguration getDecodeConfiguration(DecoderType decoderType, Site site, SystemInformation systemInformation)
|
||||||
{
|
{
|
||||||
if(decoderType == null)
|
if(decoderType == null)
|
||||||
@@ -298,7 +318,13 @@ public class SiteEditor extends GridPane
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the user selected site info into this editor.
|
||||||
|
* @param site to load
|
||||||
|
* @param system for the site
|
||||||
|
* @param systemInformation for the site
|
||||||
|
* @param decoder to lookup supplemental information
|
||||||
|
*/
|
||||||
public void setSite(EnrichedSite site, System system, SystemInformation systemInformation, RadioReferenceDecoder decoder)
|
public void setSite(EnrichedSite site, System system, SystemInformation systemInformation, RadioReferenceDecoder decoder)
|
||||||
{
|
{
|
||||||
mCurrentSite = site;
|
mCurrentSite = site;
|
||||||
@@ -346,6 +372,29 @@ public class SiteEditor extends GridPane
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Flavor flavor = decoder.getFlavor(system);
|
||||||
|
|
||||||
|
if(flavor != null && flavor.getName() != null && flavor.getName().equals(PHASE_2_FLAVOR))
|
||||||
|
{
|
||||||
|
getP25ControlLabel().setVisible(true);
|
||||||
|
getTdmaControlToggleButton().setVisible(true);
|
||||||
|
getFdmaControlToggleButton().setVisible(true);
|
||||||
|
if(site.getSite().getModulation() != null && site.getSite().getModulation().contains(PHASE_2_TDMA_MODULATION))
|
||||||
|
{
|
||||||
|
getTdmaControlToggleButton().setSelected(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getFdmaControlToggleButton().setSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getP25ControlLabel().setVisible(false);
|
||||||
|
getTdmaControlToggleButton().setVisible(false);
|
||||||
|
getFdmaControlToggleButton().setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
getCreateChannelConfigurationButton().setVisible(true);
|
getCreateChannelConfigurationButton().setVisible(true);
|
||||||
getGoToChannelEditorCheckBox().setVisible(true);
|
getGoToChannelEditorCheckBox().setVisible(true);
|
||||||
getSystemTextField().setText(system.getName());
|
getSystemTextField().setText(system.getName());
|
||||||
@@ -514,10 +563,13 @@ public class SiteEditor extends GridPane
|
|||||||
|
|
||||||
DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem);
|
DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem);
|
||||||
|
|
||||||
//Change a phase 2 system to use the phase 1 control channel
|
//Phase 2 - inspect the site modulation and use Phase 2 for TDMA control channel, otherwise Phase 1
|
||||||
if(decoderType == DecoderType.P25_PHASE2)
|
if(decoderType == DecoderType.P25_PHASE2)
|
||||||
{
|
{
|
||||||
decoderType = DecoderType.P25_PHASE1;
|
if(getFdmaControlToggleButton().isSelected())
|
||||||
|
{
|
||||||
|
decoderType = DecoderType.P25_PHASE1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(), mCurrentSystemInformation));
|
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(), mCurrentSystemInformation));
|
||||||
@@ -587,10 +639,13 @@ public class SiteEditor extends GridPane
|
|||||||
|
|
||||||
DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem);
|
DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem);
|
||||||
|
|
||||||
//Change a phase 2 system to use the phase 1 control channel
|
//Phase 2 - inspect the site modulation and use Phase 2 for TDMA control channel, otherwise Phase 1
|
||||||
if(decoderType == DecoderType.P25_PHASE2)
|
if(decoderType == DecoderType.P25_PHASE2)
|
||||||
{
|
{
|
||||||
decoderType = DecoderType.P25_PHASE1;
|
if(getFdmaControlToggleButton().isSelected())
|
||||||
|
{
|
||||||
|
decoderType = DecoderType.P25_PHASE1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(),
|
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(),
|
||||||
@@ -685,6 +740,16 @@ public class SiteEditor extends GridPane
|
|||||||
Channel channel = getChannelTemplate();
|
Channel channel = getChannelTemplate();
|
||||||
|
|
||||||
DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem);
|
DecoderType decoderType = mRadioReferenceDecoder.getDecoderType(mCurrentSystem);
|
||||||
|
|
||||||
|
//Phase 2 - inspect the site modulation and use Phase 2 for TDMA control channel, otherwise Phase 1
|
||||||
|
if(decoderType == DecoderType.P25_PHASE2)
|
||||||
|
{
|
||||||
|
if(getFdmaControlToggleButton().isSelected())
|
||||||
|
{
|
||||||
|
decoderType = DecoderType.P25_PHASE1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(),
|
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(),
|
||||||
mCurrentSystemInformation));
|
mCurrentSystemInformation));
|
||||||
|
|
||||||
@@ -750,6 +815,70 @@ public class SiteEditor extends GridPane
|
|||||||
return (long)(siteFrequency.getFrequency() * 1E6);
|
return (long)(siteFrequency.getFrequency() * 1E6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P25 Phase 2 control channel label.
|
||||||
|
*/
|
||||||
|
private Label getP25ControlLabel()
|
||||||
|
{
|
||||||
|
if(mP25ControlLabel == null)
|
||||||
|
{
|
||||||
|
mP25ControlLabel = new Label("Control");
|
||||||
|
mP25ControlLabel.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mP25ControlLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P25 Phase 2 control channel type (TDMA vs FDMA) selection buttons.
|
||||||
|
*/
|
||||||
|
private SegmentedButton getP25ControlSegmentedButton()
|
||||||
|
{
|
||||||
|
if(mP25ControlSegmentedButton == null)
|
||||||
|
{
|
||||||
|
mP25ControlSegmentedButton = new SegmentedButton(getFdmaControlToggleButton(), getTdmaControlToggleButton());
|
||||||
|
mP25ControlSegmentedButton.getStyleClass().add(SegmentedButton.STYLE_CLASS_DARK);
|
||||||
|
mP25ControlSegmentedButton.getToggleGroup().selectedToggleProperty()
|
||||||
|
.addListener((observable, oldValue, newValue) -> {
|
||||||
|
//Ensure that one button is always selected
|
||||||
|
if(newValue == null)
|
||||||
|
{
|
||||||
|
oldValue.setSelected(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mP25ControlSegmentedButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P25 Phase 2 with FDMA (phase 1) control channel selection button.
|
||||||
|
*/
|
||||||
|
private ToggleButton getFdmaControlToggleButton()
|
||||||
|
{
|
||||||
|
if(mFdmaControlToggleButton == null)
|
||||||
|
{
|
||||||
|
mFdmaControlToggleButton = new ToggleButton("FDMA Phase 1");
|
||||||
|
mFdmaControlToggleButton.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFdmaControlToggleButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P25 Phase 2 with TDMA (phase 2) control channel selection button.
|
||||||
|
*/
|
||||||
|
private ToggleButton getTdmaControlToggleButton()
|
||||||
|
{
|
||||||
|
if(mTdmaControlToggleButton == null)
|
||||||
|
{
|
||||||
|
mTdmaControlToggleButton = new ToggleButton("TDMA Phase 2");
|
||||||
|
mTdmaControlToggleButton.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTdmaControlToggleButton;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flashes the alias list combobox to let the user know that they must select an alias list
|
* Flashes the alias list combobox to let the user know that they must select an alias list
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.gui.playlist.radioreference;
|
package io.github.dsheirer.gui.playlist.radioreference;
|
||||||
|
|
||||||
|
import com.google.common.collect.Ordering;
|
||||||
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.rrapi.type.Flavor;
|
import io.github.dsheirer.rrapi.type.Flavor;
|
||||||
@@ -42,6 +43,8 @@ import javafx.scene.layout.Priority;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
@@ -126,6 +129,7 @@ public class SystemSiteSelectionEditor extends GridPane
|
|||||||
getFlavorLabel().setText(flavor != null ? flavor.getName() : null);
|
getFlavorLabel().setText(flavor != null ? flavor.getName() : null);
|
||||||
Voice voice = decoder.getVoice(system);
|
Voice voice = decoder.getVoice(system);
|
||||||
getVoiceLabel().setText(voice != null ? voice.getName() : null);
|
getVoiceLabel().setText(voice != null ? voice.getName() : null);
|
||||||
|
Collections.sort(sites, Ordering.natural());
|
||||||
getSiteTableView().getItems().addAll(sites);
|
getSiteTableView().getItems().addAll(sites);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -212,27 +216,33 @@ public class SystemSiteSelectionEditor extends GridPane
|
|||||||
mSiteTableView = new TableView<>();
|
mSiteTableView = new TableView<>();
|
||||||
mSiteTableView.setPlaceholder(getPlaceholderLabel());
|
mSiteTableView.setPlaceholder(getPlaceholderLabel());
|
||||||
|
|
||||||
TableColumn numberColumn = new TableColumn();
|
TableColumn systemColumn = new TableColumn();
|
||||||
numberColumn.setText("Site");
|
systemColumn.setText("System");
|
||||||
numberColumn.setCellValueFactory(new PropertyValueFactory<>("siteNumber"));
|
systemColumn.setCellValueFactory(new PropertyValueFactory<>("systemFormatted"));
|
||||||
numberColumn.setPrefWidth(75);
|
systemColumn.setPrefWidth(60);
|
||||||
|
|
||||||
TableColumn rfssColumn = new TableColumn();
|
TableColumn rfssColumn = new TableColumn();
|
||||||
rfssColumn.setText("RFSS");
|
rfssColumn.setText("RFSS");
|
||||||
rfssColumn.setCellValueFactory(new PropertyValueFactory<>("rfss"));
|
rfssColumn.setCellValueFactory(new PropertyValueFactory<>("rfssFormatted"));
|
||||||
rfssColumn.setPrefWidth(60);
|
rfssColumn.setPrefWidth(60);
|
||||||
|
|
||||||
TableColumn countyColumn = new TableColumn();
|
TableColumn siteColumn = new TableColumn();
|
||||||
countyColumn.setText("County");
|
siteColumn.setText("Site");
|
||||||
countyColumn.setCellValueFactory(new PropertyValueFactory<>("countyName"));
|
siteColumn.setCellValueFactory(new PropertyValueFactory<>("siteFormatted"));
|
||||||
countyColumn.setPrefWidth(125);
|
siteColumn.setPrefWidth(75);
|
||||||
|
|
||||||
|
TableColumn countyNameColumn = new TableColumn();
|
||||||
|
countyNameColumn.setText("County");
|
||||||
|
countyNameColumn.setCellValueFactory(new PropertyValueFactory<>("countyName"));
|
||||||
|
countyNameColumn.setPrefWidth(125);
|
||||||
|
|
||||||
TableColumn descriptionColumn = new TableColumn();
|
TableColumn descriptionColumn = new TableColumn();
|
||||||
descriptionColumn.setText("Name");
|
descriptionColumn.setText("Name");
|
||||||
descriptionColumn.setCellValueFactory(new PropertyValueFactory<>("description"));
|
descriptionColumn.setCellValueFactory(new PropertyValueFactory<>("description"));
|
||||||
descriptionColumn.setPrefWidth(400);
|
descriptionColumn.setPrefWidth(400);
|
||||||
|
|
||||||
mSiteTableView.getColumns().addAll(numberColumn, rfssColumn, countyColumn, descriptionColumn);
|
mSiteTableView.getColumns().addAll(systemColumn, rfssColumn, siteColumn, countyNameColumn,
|
||||||
|
descriptionColumn);
|
||||||
mSiteTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
mSiteTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||||
mSiteTableView.getSelectionModel().selectedItemProperty()
|
mSiteTableView.getSelectionModel().selectedItemProperty()
|
||||||
.addListener((observable, oldValue, selected) ->
|
.addListener((observable, oldValue, selected) ->
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.viewer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||||
|
import javafx.geometry.HPos;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.ColumnConstraints;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Viewer for channel start processing requests
|
||||||
|
*/
|
||||||
|
public class ChannelStartProcessingRequestViewer extends HBox
|
||||||
|
{
|
||||||
|
private Label mChannelConfigLabel;
|
||||||
|
private Label mChannelDescriptorLabel;
|
||||||
|
private Label mTrafficChannelManagerLabel;
|
||||||
|
private Label mPreloadDataContentLabel;
|
||||||
|
private Label mParentDecodeHistoryLabel;
|
||||||
|
private Label mChildDecodeHistoryLabel;
|
||||||
|
private IdentifierCollectionViewer mIdentifierCollectionViewer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public ChannelStartProcessingRequestViewer()
|
||||||
|
{
|
||||||
|
GridPane gridPane = new GridPane();
|
||||||
|
gridPane.setVgap(5);
|
||||||
|
gridPane.setHgap(5);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Configuration:"), 0, 0);
|
||||||
|
GridPane.setHgrow(getChannelConfigLabel(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getChannelConfigLabel(), 1, 0);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Channel:"), 0, 1);
|
||||||
|
GridPane.setHgrow(getChannelDescriptorLabel(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getChannelDescriptorLabel(), 1, 1);
|
||||||
|
|
||||||
|
gridPane.add(new Label("TCM:"), 0, 2);
|
||||||
|
GridPane.setHgrow(getTrafficChannelManagerLabel(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getTrafficChannelManagerLabel(), 1, 2);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Preload Items:"), 0, 3);
|
||||||
|
GridPane.setHgrow(getPreloadDataContentLabel(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getPreloadDataContentLabel(), 1, 3);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Parent History:"), 0, 4);
|
||||||
|
GridPane.setHgrow(getParentDecodeHistoryLabel(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getParentDecodeHistoryLabel(), 1, 4);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Child History:"), 0, 5);
|
||||||
|
GridPane.setHgrow(getChildDecodeHistoryLabel(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getChildDecodeHistoryLabel(), 1, 5);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Identifiers"), 0, 6);
|
||||||
|
GridPane.setHgrow(getIdentifierCollectionViewer(), Priority.ALWAYS);
|
||||||
|
getIdentifierCollectionViewer().setPrefHeight(120);
|
||||||
|
gridPane.add(getIdentifierCollectionViewer(), 0, 7, 2, 1);
|
||||||
|
|
||||||
|
ColumnConstraints cc0 = new ColumnConstraints();
|
||||||
|
cc0.setHalignment(HPos.RIGHT);
|
||||||
|
gridPane.getColumnConstraints().add(cc0);
|
||||||
|
|
||||||
|
getChildren().add(gridPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(ChannelStartProcessingRequest request)
|
||||||
|
{
|
||||||
|
if(request != null)
|
||||||
|
{
|
||||||
|
if(request.getChannel() != null)
|
||||||
|
{
|
||||||
|
getChannelConfigLabel().setText(request.getChannel().toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getChannelConfigLabel().setText("None");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.getChannelDescriptor() != null)
|
||||||
|
{
|
||||||
|
getChannelDescriptorLabel().setText(request.getChannelDescriptor().toString() + " " +
|
||||||
|
request.getChannelDescriptor().getDownlinkFrequency() + " MHz");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getChannelDescriptorLabel().setText("None");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.getChildDecodeEventHistory() != null)
|
||||||
|
{
|
||||||
|
getChildDecodeHistoryLabel().setText(request.getChildDecodeEventHistory().getItems().size() + " items");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getChildDecodeHistoryLabel().setText("None");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.getParentDecodeEventHistory() != null)
|
||||||
|
{
|
||||||
|
getParentDecodeHistoryLabel().setText(request.getParentDecodeEventHistory().getItems().size() + " items");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getParentDecodeHistoryLabel().setText("None");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.getPreloadDataContents() != null)
|
||||||
|
{
|
||||||
|
getPreloadDataContentLabel().setText(request.getPreloadDataContents().size() + " items");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getPreloadDataContentLabel().setText("None");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.getTrafficChannelManager() != null)
|
||||||
|
{
|
||||||
|
getTrafficChannelManagerLabel().setText(request.getTrafficChannelManager().getClass().getName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getTrafficChannelManagerLabel().setText("None:");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.getIdentifierCollection() != null)
|
||||||
|
{
|
||||||
|
getIdentifierCollectionViewer().set(request.getIdentifierCollection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getChannelDescriptorLabel().setText(null);
|
||||||
|
getChildDecodeHistoryLabel().setText(null);
|
||||||
|
getChannelConfigLabel().setText(null);
|
||||||
|
getIdentifierCollectionViewer().set(null);
|
||||||
|
getParentDecodeHistoryLabel().setText(null);
|
||||||
|
getPreloadDataContentLabel().setText(null);
|
||||||
|
getTrafficChannelManagerLabel().setText(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Label getChannelConfigLabel()
|
||||||
|
{
|
||||||
|
if(mChannelConfigLabel == null)
|
||||||
|
{
|
||||||
|
mChannelConfigLabel = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannelConfigLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Label getChannelDescriptorLabel()
|
||||||
|
{
|
||||||
|
if(mChannelDescriptorLabel == null)
|
||||||
|
{
|
||||||
|
mChannelDescriptorLabel = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannelDescriptorLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Label getTrafficChannelManagerLabel()
|
||||||
|
{
|
||||||
|
if(mTrafficChannelManagerLabel == null)
|
||||||
|
{
|
||||||
|
mTrafficChannelManagerLabel = new Label();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTrafficChannelManagerLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Label getPreloadDataContentLabel()
|
||||||
|
{
|
||||||
|
if(mPreloadDataContentLabel == null)
|
||||||
|
{
|
||||||
|
mPreloadDataContentLabel = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPreloadDataContentLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Label getParentDecodeHistoryLabel()
|
||||||
|
{
|
||||||
|
if(mParentDecodeHistoryLabel == null)
|
||||||
|
{
|
||||||
|
mParentDecodeHistoryLabel = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mParentDecodeHistoryLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Label getChildDecodeHistoryLabel()
|
||||||
|
{
|
||||||
|
if(mChildDecodeHistoryLabel == null)
|
||||||
|
{
|
||||||
|
mChildDecodeHistoryLabel = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChildDecodeHistoryLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IdentifierCollectionViewer getIdentifierCollectionViewer()
|
||||||
|
{
|
||||||
|
if(mIdentifierCollectionViewer == null)
|
||||||
|
{
|
||||||
|
mIdentifierCollectionViewer = new IdentifierCollectionViewer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifierCollectionViewer;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.viewer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaFX identifier collection viewer
|
||||||
|
*/
|
||||||
|
public class IdentifierCollectionViewer extends VBox
|
||||||
|
{
|
||||||
|
private TableView<Identifier> mIdentifierTableView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public IdentifierCollectionViewer()
|
||||||
|
{
|
||||||
|
GridPane gridPane = new GridPane();
|
||||||
|
GridPane.setHgrow(getIdentifierTableView(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getIdentifierTableView(), 0, 0);
|
||||||
|
getChildren().add(gridPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(IdentifierCollection identifierCollection)
|
||||||
|
{
|
||||||
|
getIdentifierTableView().getItems().clear();
|
||||||
|
|
||||||
|
if(identifierCollection != null)
|
||||||
|
{
|
||||||
|
getIdentifierTableView().getItems().addAll(identifierCollection.getIdentifiers());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableView<Identifier> getIdentifierTableView()
|
||||||
|
{
|
||||||
|
if(mIdentifierTableView == null)
|
||||||
|
{
|
||||||
|
mIdentifierTableView = new TableView<>();
|
||||||
|
|
||||||
|
TableColumn classColumn = new TableColumn();
|
||||||
|
classColumn.setPrefWidth(110);
|
||||||
|
classColumn.setText("Class");
|
||||||
|
classColumn.setCellValueFactory(new PropertyValueFactory<>("identifierClass"));
|
||||||
|
|
||||||
|
TableColumn formColumn = new TableColumn();
|
||||||
|
formColumn.setPrefWidth(160);
|
||||||
|
formColumn.setText("Form");
|
||||||
|
formColumn.setCellValueFactory(new PropertyValueFactory<>("form"));
|
||||||
|
|
||||||
|
TableColumn roleColumn = new TableColumn();
|
||||||
|
roleColumn.setPrefWidth(110);
|
||||||
|
roleColumn.setText("Role");
|
||||||
|
roleColumn.setCellValueFactory(new PropertyValueFactory<>("role"));
|
||||||
|
|
||||||
|
TableColumn valueColumn = new TableColumn();
|
||||||
|
valueColumn.setPrefWidth(160);
|
||||||
|
valueColumn.setText("Value");
|
||||||
|
valueColumn.setCellValueFactory(new PropertyValueFactory<>("value"));
|
||||||
|
|
||||||
|
mIdentifierTableView.getColumns().addAll(classColumn, formColumn, roleColumn, valueColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifierTableView;
|
||||||
|
}
|
||||||
|
}
|
||||||
159
src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java
Normal file
159
src/main/java/io/github/dsheirer/gui/viewer/MessagePackage.java
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.viewer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||||
|
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||||
|
import io.github.dsheirer.message.IMessage;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEventSnapshot;
|
||||||
|
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper class that combines a single message with any decoder state events and decoded events that were produced by a
|
||||||
|
* decoder state.
|
||||||
|
*/
|
||||||
|
public class MessagePackage
|
||||||
|
{
|
||||||
|
private IMessage mMessage;
|
||||||
|
private List<DecoderStateEvent> mDecoderStateEvents = new ArrayList<>();
|
||||||
|
private List<DecodeEventSnapshot> mDecodeEvents = new ArrayList<>();
|
||||||
|
private ChannelStartProcessingRequest mChannelStartProcessingRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param message for this instance
|
||||||
|
*/
|
||||||
|
public MessagePackage(IMessage message)
|
||||||
|
{
|
||||||
|
mMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message for this instance
|
||||||
|
*/
|
||||||
|
public IMessage getMessage()
|
||||||
|
{
|
||||||
|
return mMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of decoder state events
|
||||||
|
*/
|
||||||
|
public List<DecoderStateEvent> getDecoderStateEvents()
|
||||||
|
{
|
||||||
|
return mDecoderStateEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the decoder state event to this instance
|
||||||
|
*/
|
||||||
|
public void add(DecoderStateEvent event)
|
||||||
|
{
|
||||||
|
mDecoderStateEvents.add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of decode event snapshots
|
||||||
|
*/
|
||||||
|
public List<DecodeEventSnapshot> getDecodeEvents()
|
||||||
|
{
|
||||||
|
return mDecodeEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the decode event to this instance
|
||||||
|
*/
|
||||||
|
public void add(DecodeEventSnapshot event)
|
||||||
|
{
|
||||||
|
mDecodeEvents.add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the channel start processing request
|
||||||
|
*/
|
||||||
|
public void add(ChannelStartProcessingRequest request)
|
||||||
|
{
|
||||||
|
mChannelStartProcessingRequest = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message timeslot
|
||||||
|
*/
|
||||||
|
public int getTimeslot()
|
||||||
|
{
|
||||||
|
return getMessage().getTimeslot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message timestamp
|
||||||
|
*/
|
||||||
|
public long getTimestamp()
|
||||||
|
{
|
||||||
|
return getMessage().getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message validity flag
|
||||||
|
*/
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return getMessage().isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message string representation
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getMessage().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of decode events
|
||||||
|
*/
|
||||||
|
public int getDecodeEventCount()
|
||||||
|
{
|
||||||
|
return mDecodeEvents.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of decoder state events
|
||||||
|
*/
|
||||||
|
public int getDecoderStateEventCount()
|
||||||
|
{
|
||||||
|
return mDecoderStateEvents.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count (0 or 1) of channel start processing requests.
|
||||||
|
*/
|
||||||
|
public int getChannelStartProcessingRequestCount()
|
||||||
|
{
|
||||||
|
return mChannelStartProcessingRequest == null ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelStartProcessingRequest getChannelStartProcessingRequest()
|
||||||
|
{
|
||||||
|
return mChannelStartProcessingRequest;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.viewer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEventSnapshot;
|
||||||
|
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.GridPane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaFX message package details viewer
|
||||||
|
*/
|
||||||
|
public class MessagePackageViewer extends VBox
|
||||||
|
{
|
||||||
|
private Label mMessageLabel;
|
||||||
|
private TableView<DecoderStateEvent> mDecoderStateEventTableView;
|
||||||
|
private TableView<DecodeEventSnapshot> mDecodeEventTableView;
|
||||||
|
private IdentifierCollectionViewer mIdentifierCollectionViewer;
|
||||||
|
private ChannelStartProcessingRequestViewer mChannelStartProcessingRequestViewer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public MessagePackageViewer()
|
||||||
|
{
|
||||||
|
GridPane gridPane = new GridPane();
|
||||||
|
gridPane.setHgap(5);
|
||||||
|
gridPane.setVgap(5);
|
||||||
|
Label messageLabel = new Label("Message:");
|
||||||
|
gridPane.add(messageLabel, 0, 0);
|
||||||
|
GridPane.setHgrow(getMessageLabel(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getMessageLabel(), 1, 0);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Decoder State Events"), 0, 1);
|
||||||
|
getDecoderStateEventTableView().setPrefHeight(120);
|
||||||
|
GridPane.setHgrow(getDecoderStateEventTableView(), Priority.NEVER);
|
||||||
|
gridPane.add(getDecoderStateEventTableView(), 0, 2);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Decode Events"), 1, 1);
|
||||||
|
getDecodeEventTableView().setPrefHeight(120);
|
||||||
|
GridPane.setHgrow(getDecodeEventTableView(), Priority.ALWAYS);
|
||||||
|
gridPane.add(getDecodeEventTableView(), 1, 2);
|
||||||
|
|
||||||
|
gridPane.add(new Label("Channel Start Processing Request"), 0, 3);
|
||||||
|
gridPane.add(getChannelStartProcessingRequestViewer(), 0, 4);
|
||||||
|
|
||||||
|
getIdentifierCollectionViewer().setPrefHeight(120);
|
||||||
|
gridPane.add(new Label("Selected Decode Event Identifiers"), 1, 3);
|
||||||
|
gridPane.add(getIdentifierCollectionViewer(), 1, 4);
|
||||||
|
|
||||||
|
getChildren().add(gridPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(MessagePackage messagePackage)
|
||||||
|
{
|
||||||
|
getMessageLabel().setText(null);
|
||||||
|
getDecoderStateEventTableView().getItems().clear();
|
||||||
|
getDecodeEventTableView().getItems().clear();
|
||||||
|
getChannelStartProcessingRequestViewer().set(null);
|
||||||
|
|
||||||
|
if(messagePackage != null)
|
||||||
|
{
|
||||||
|
String message = messagePackage.toString();
|
||||||
|
if(message.length() > 40)
|
||||||
|
{
|
||||||
|
message = message.substring(0, 40) + "...";
|
||||||
|
}
|
||||||
|
getMessageLabel().setText(message);
|
||||||
|
getDecoderStateEventTableView().getItems().addAll(messagePackage.getDecoderStateEvents());
|
||||||
|
getDecodeEventTableView().getItems().addAll(messagePackage.getDecodeEvents());
|
||||||
|
getChannelStartProcessingRequestViewer().set(messagePackage.getChannelStartProcessingRequest());
|
||||||
|
|
||||||
|
if(getDecodeEventTableView().getItems().size() > 0)
|
||||||
|
{
|
||||||
|
getDecodeEventTableView().getSelectionModel().select(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IdentifierCollectionViewer getIdentifierCollectionViewer()
|
||||||
|
{
|
||||||
|
if(mIdentifierCollectionViewer == null)
|
||||||
|
{
|
||||||
|
mIdentifierCollectionViewer = new IdentifierCollectionViewer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifierCollectionViewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Label getMessageLabel()
|
||||||
|
{
|
||||||
|
if(mMessageLabel == null)
|
||||||
|
{
|
||||||
|
mMessageLabel = new Label();
|
||||||
|
mMessageLabel.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMessageLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableView<DecoderStateEvent> getDecoderStateEventTableView()
|
||||||
|
{
|
||||||
|
if(mDecoderStateEventTableView == null)
|
||||||
|
{
|
||||||
|
mDecoderStateEventTableView = new TableView<>();
|
||||||
|
|
||||||
|
TableColumn timeslotColumn = new TableColumn();
|
||||||
|
timeslotColumn.setPrefWidth(110);
|
||||||
|
timeslotColumn.setText("Timeslot");
|
||||||
|
timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot"));
|
||||||
|
|
||||||
|
TableColumn stateColumn = new TableColumn();
|
||||||
|
stateColumn.setPrefWidth(110);
|
||||||
|
stateColumn.setText("State");
|
||||||
|
stateColumn.setCellValueFactory(new PropertyValueFactory<>("state"));
|
||||||
|
|
||||||
|
TableColumn eventColumn = new TableColumn();
|
||||||
|
eventColumn.setPrefWidth(110);
|
||||||
|
eventColumn.setText("Event");
|
||||||
|
eventColumn.setCellValueFactory(new PropertyValueFactory<>("event"));
|
||||||
|
|
||||||
|
TableColumn frequencyColumn = new TableColumn();
|
||||||
|
frequencyColumn.setPrefWidth(110);
|
||||||
|
frequencyColumn.setText("Frequency");
|
||||||
|
frequencyColumn.setCellValueFactory(new PropertyValueFactory<>("frequency"));
|
||||||
|
|
||||||
|
mDecoderStateEventTableView.getColumns().addAll(timeslotColumn, stateColumn, eventColumn, frequencyColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDecoderStateEventTableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableView<DecodeEventSnapshot> getDecodeEventTableView()
|
||||||
|
{
|
||||||
|
if(mDecodeEventTableView == null)
|
||||||
|
{
|
||||||
|
mDecodeEventTableView = new TableView<>();
|
||||||
|
mDecodeEventTableView.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
|
TableColumn startTimeColumn = new TableColumn();
|
||||||
|
startTimeColumn.setPrefWidth(110);
|
||||||
|
startTimeColumn.setText("Start");
|
||||||
|
startTimeColumn.setCellValueFactory(new PropertyValueFactory<>("timeStart"));
|
||||||
|
|
||||||
|
TableColumn durationColumn = new TableColumn();
|
||||||
|
durationColumn.setPrefWidth(110);
|
||||||
|
durationColumn.setText("Duration");
|
||||||
|
durationColumn.setCellValueFactory(new PropertyValueFactory<>("duration"));
|
||||||
|
|
||||||
|
TableColumn typeColumn = new TableColumn();
|
||||||
|
typeColumn.setPrefWidth(130);
|
||||||
|
typeColumn.setText("Type");
|
||||||
|
typeColumn.setCellValueFactory(new PropertyValueFactory<>("eventType"));
|
||||||
|
|
||||||
|
TableColumn channelDescriptorColumn = new TableColumn();
|
||||||
|
channelDescriptorColumn.setPrefWidth(110);
|
||||||
|
channelDescriptorColumn.setText("Channel");
|
||||||
|
channelDescriptorColumn.setCellValueFactory(new PropertyValueFactory<>("channelDescriptor"));
|
||||||
|
|
||||||
|
TableColumn frequencyColumn = new TableColumn();
|
||||||
|
frequencyColumn.setPrefWidth(110);
|
||||||
|
frequencyColumn.setText("Frequency");
|
||||||
|
frequencyColumn.setCellValueFactory(new PropertyValueFactory<>("frequency"));
|
||||||
|
|
||||||
|
TableColumn hashcodeColumn = new TableColumn();
|
||||||
|
hashcodeColumn.setPrefWidth(100);
|
||||||
|
hashcodeColumn.setText("Hash ID");
|
||||||
|
hashcodeColumn.setCellValueFactory(new PropertyValueFactory<>("originalHashCode"));
|
||||||
|
|
||||||
|
TableColumn detailsColumn = new TableColumn();
|
||||||
|
detailsColumn.setPrefWidth(500);
|
||||||
|
detailsColumn.setText("Details");
|
||||||
|
detailsColumn.setCellValueFactory(new PropertyValueFactory<>("details"));
|
||||||
|
|
||||||
|
mDecodeEventTableView.getColumns().addAll(startTimeColumn, durationColumn, typeColumn, channelDescriptorColumn,
|
||||||
|
frequencyColumn, hashcodeColumn, detailsColumn);
|
||||||
|
|
||||||
|
mDecodeEventTableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) ->
|
||||||
|
{
|
||||||
|
if(newValue != null)
|
||||||
|
{
|
||||||
|
getIdentifierCollectionViewer().set(newValue.getIdentifierCollection());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getIdentifierCollectionViewer().set(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDecodeEventTableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChannelStartProcessingRequestViewer getChannelStartProcessingRequestViewer()
|
||||||
|
{
|
||||||
|
if(mChannelStartProcessingRequestViewer == null)
|
||||||
|
{
|
||||||
|
mChannelStartProcessingRequestViewer = new ChannelStartProcessingRequestViewer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannelStartProcessingRequestViewer;
|
||||||
|
}
|
||||||
|
}
|
||||||
107
src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java
Normal file
107
src/main/java/io/github/dsheirer/gui/viewer/MessagePackager.java
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.viewer;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||||
|
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||||
|
import io.github.dsheirer.message.IMessage;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEventSnapshot;
|
||||||
|
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.event.IDecodeEventListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility for combining a message and decoder state events.
|
||||||
|
*/
|
||||||
|
public class MessagePackager
|
||||||
|
{
|
||||||
|
private MessagePackage mMessagePackage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public MessagePackager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the message and creates a new MessageWithEvents instance, wrapping the message, ready to also receive any
|
||||||
|
* decode events and decoder state events. The previous message with events is overwritten.
|
||||||
|
* @param message to wrap.
|
||||||
|
*/
|
||||||
|
public void add(IMessage message)
|
||||||
|
{
|
||||||
|
mMessagePackage = new MessagePackage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the current message with events.
|
||||||
|
*/
|
||||||
|
public MessagePackage getMessageWithEvents()
|
||||||
|
{
|
||||||
|
return mMessagePackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the decoder state event to the current message with events.
|
||||||
|
* @param event to add
|
||||||
|
*/
|
||||||
|
public void add(DecoderStateEvent event)
|
||||||
|
{
|
||||||
|
if(mMessagePackage != null)
|
||||||
|
{
|
||||||
|
mMessagePackage.add(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the decode event to the current message with events.
|
||||||
|
* @param event to add
|
||||||
|
*/
|
||||||
|
public void add(IDecodeEvent event)
|
||||||
|
{
|
||||||
|
if(mMessagePackage != null)
|
||||||
|
{
|
||||||
|
if(event instanceof DecodeEvent decodeEvent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DecodeEventSnapshot snapshot = decodeEvent.getSnapshot();
|
||||||
|
mMessagePackage.add(snapshot);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription to receive channel start processing requests via the event bus from the traffic channel manager
|
||||||
|
* @param request sent from the traffic channel manager.
|
||||||
|
*/
|
||||||
|
@Subscribe
|
||||||
|
public void process(ChannelStartProcessingRequest request)
|
||||||
|
{
|
||||||
|
mMessagePackage.add(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,20 +41,21 @@ import org.slf4j.LoggerFactory;
|
|||||||
/**
|
/**
|
||||||
* Utility application to load and view .bits recording file with the messages fully parsed.
|
* Utility application to load and view .bits recording file with the messages fully parsed.
|
||||||
*
|
*
|
||||||
* Supported Protocols: DMR and APCO25 Phase 1.
|
* Supported Protocols: DMR, APCO25 Phase 1 and Phase 2.
|
||||||
*/
|
*/
|
||||||
public class RecordingViewer extends VBox
|
public class MessageRecordingViewer extends VBox
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(RecordingViewer.class);
|
private static final Logger mLog = LoggerFactory.getLogger(MessageRecordingViewer.class);
|
||||||
private MenuBar mMenuBar;
|
private MenuBar mMenuBar;
|
||||||
private TabPane mTabPane;
|
private TabPane mTabPane;
|
||||||
private int mTabCounterDmr = 1;
|
private int mTabCounterDmr = 1;
|
||||||
private int mTabCounterP25P1 = 1;
|
private int mTabCounterP25P1 = 1;
|
||||||
|
private int mTabCounterP25P2 = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
*/
|
*/
|
||||||
public RecordingViewer()
|
public MessageRecordingViewer()
|
||||||
{
|
{
|
||||||
VBox.setVgrow(getTabPane(), Priority.ALWAYS);
|
VBox.setVgrow(getTabPane(), Priority.ALWAYS);
|
||||||
getChildren().addAll(getMenuBar(), getTabPane());
|
getChildren().addAll(getMenuBar(), getTabPane());
|
||||||
@@ -74,13 +75,19 @@ public class RecordingViewer extends VBox
|
|||||||
getTabPane().getTabs().add(tab);
|
getTabPane().getTabs().add(tab);
|
||||||
getTabPane().getSelectionModel().select(tab);
|
getTabPane().getSelectionModel().select(tab);
|
||||||
});
|
});
|
||||||
MenuItem p25p1MenuItem = new MenuItem("P25P1");
|
MenuItem p25p1MenuItem = new MenuItem("P25 Phase 1");
|
||||||
p25p1MenuItem.onActionProperty().set(event -> {
|
p25p1MenuItem.onActionProperty().set(event -> {
|
||||||
Tab tab = new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer());
|
Tab tab = new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer());
|
||||||
getTabPane().getTabs().add(tab);
|
getTabPane().getTabs().add(tab);
|
||||||
getTabPane().getSelectionModel().select(tab);
|
getTabPane().getSelectionModel().select(tab);
|
||||||
});
|
});
|
||||||
createNewViewerMenu.getItems().addAll(dmrMenuItem, p25p1MenuItem);
|
MenuItem p25p2MenuItem = new MenuItem("P25 Phase 2");
|
||||||
|
p25p2MenuItem.onActionProperty().set(event -> {
|
||||||
|
Tab tab = new LabeledTab("P25P2-" + mTabCounterP25P2++, new P25P2Viewer());
|
||||||
|
getTabPane().getTabs().add(tab);
|
||||||
|
getTabPane().getSelectionModel().select(tab);
|
||||||
|
});
|
||||||
|
createNewViewerMenu.getItems().addAll(dmrMenuItem, p25p1MenuItem, p25p2MenuItem);
|
||||||
|
|
||||||
MenuItem exitMenu = new MenuItem("Exit");
|
MenuItem exitMenu = new MenuItem("Exit");
|
||||||
exitMenu.onActionProperty().set(event -> ((Stage)getScene().getWindow()).close());
|
exitMenu.onActionProperty().set(event -> ((Stage)getScene().getWindow()).close());
|
||||||
@@ -102,6 +109,7 @@ public class RecordingViewer extends VBox
|
|||||||
mTabPane.setMaxHeight(Double.MAX_VALUE);
|
mTabPane.setMaxHeight(Double.MAX_VALUE);
|
||||||
mTabPane.getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer()));
|
mTabPane.getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer()));
|
||||||
mTabPane.getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()));
|
mTabPane.getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()));
|
||||||
|
mTabPane.getTabs().add(new LabeledTab("P25P2-" + mTabCounterP25P2++, new P25P2Viewer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mTabPane;
|
return mTabPane;
|
||||||
@@ -151,11 +159,6 @@ public class RecordingViewer extends VBox
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public LabeledTab(String label)
|
|
||||||
{
|
|
||||||
this(label, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
@@ -165,7 +168,7 @@ public class RecordingViewer extends VBox
|
|||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception
|
public void start(Stage primaryStage) throws Exception
|
||||||
{
|
{
|
||||||
Scene scene = new Scene(new RecordingViewer(), 1100, 800);
|
Scene scene = new Scene(new MessageRecordingViewer(), 1100, 800);
|
||||||
primaryStage.setTitle("Message Recording Viewer (.bits)");
|
primaryStage.setTitle("Message Recording Viewer (.bits)");
|
||||||
primaryStage.setScene(scene);
|
primaryStage.setScene(scene);
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,8 +19,14 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.gui.viewer;
|
package io.github.dsheirer.gui.viewer;
|
||||||
|
|
||||||
import io.github.dsheirer.message.IMessage;
|
import com.google.common.eventbus.EventBus;
|
||||||
|
import io.github.dsheirer.controller.channel.Channel;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
|
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||||
import io.github.dsheirer.message.StuffBitsMessage;
|
import io.github.dsheirer.message.StuffBitsMessage;
|
||||||
|
import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageFramer;
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageFramer;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageProcessor;
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageProcessor;
|
||||||
import io.github.dsheirer.record.binary.BinaryReader;
|
import io.github.dsheirer.record.binary.BinaryReader;
|
||||||
@@ -33,8 +39,11 @@ import java.util.Set;
|
|||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
@@ -71,17 +80,20 @@ public class P25P1Viewer extends VBox
|
|||||||
private static final Logger mLog = LoggerFactory.getLogger(P25P1Viewer.class);
|
private static final Logger mLog = LoggerFactory.getLogger(P25P1Viewer.class);
|
||||||
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
|
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
|
||||||
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p1";
|
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p1";
|
||||||
|
private static final String FILE_FREQUENCY_REGEX = ".*\\d{8}_\\d{6}_(\\d{9}).*";
|
||||||
private Preferences mPreferences = Preferences.userNodeForPackage(P25P1Viewer.class);
|
private Preferences mPreferences = Preferences.userNodeForPackage(P25P1Viewer.class);
|
||||||
private Button mSelectFileButton;
|
private Button mSelectFileButton;
|
||||||
private Label mSelectedFileLabel;
|
private Label mSelectedFileLabel;
|
||||||
private TableView<IMessage> mMessageTableView;
|
private TableView<MessagePackage> mMessagePackageTableView;
|
||||||
private ObservableList<IMessage> mMessages = FXCollections.observableArrayList();
|
private ObservableList<MessagePackage> mMessagePackages = FXCollections.observableArrayList();
|
||||||
private FilteredList<IMessage> mFilteredMessages = new FilteredList<>(mMessages);
|
private FilteredList<MessagePackage> mFilteredMessagePackages = new FilteredList<>(mMessagePackages);
|
||||||
private TextField mSearchText;
|
private TextField mSearchText;
|
||||||
private TextField mFindText;
|
private TextField mFindText;
|
||||||
private Button mFindButton;
|
private Button mFindButton;
|
||||||
private Button mFindNextButton;
|
private Button mFindNextButton;
|
||||||
private ProgressIndicator mLoadingIndicator;
|
private ProgressIndicator mLoadingIndicator;
|
||||||
|
private MessagePackageViewer mMessagePackageViewer;
|
||||||
|
private StringProperty mLoadedFile = new SimpleStringProperty();
|
||||||
|
|
||||||
public P25P1Viewer()
|
public P25P1Viewer()
|
||||||
{
|
{
|
||||||
@@ -115,9 +127,11 @@ public class P25P1Viewer extends VBox
|
|||||||
|
|
||||||
VBox.setVgrow(fileBox, Priority.NEVER);
|
VBox.setVgrow(fileBox, Priority.NEVER);
|
||||||
VBox.setVgrow(filterBox, Priority.NEVER);
|
VBox.setVgrow(filterBox, Priority.NEVER);
|
||||||
VBox.setVgrow(getMessageTableView(), Priority.ALWAYS);
|
VBox.setVgrow(getMessagePackageTableView(), Priority.ALWAYS);
|
||||||
|
VBox.setVgrow(getMessagePackageViewer(), Priority.NEVER);
|
||||||
|
|
||||||
|
getChildren().addAll(fileBox, filterBox, getMessagePackageTableView(), getMessagePackageViewer());
|
||||||
|
|
||||||
getChildren().addAll(fileBox, filterBox, getMessageTableView());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,19 +146,54 @@ public class P25P1Viewer extends VBox
|
|||||||
{
|
{
|
||||||
if(file != null && file.exists())
|
if(file != null && file.exists())
|
||||||
{
|
{
|
||||||
mMessages.clear();
|
mLoadedFile.set(file.toString());
|
||||||
|
mMessagePackages.clear();
|
||||||
getLoadingIndicator().setVisible(true);
|
getLoadingIndicator().setVisible(true);
|
||||||
getSelectedFileLabel().setText("Loading ...");
|
getSelectedFileLabel().setText("Loading ...");
|
||||||
|
|
||||||
ThreadPool.CACHED.submit(() -> {
|
ThreadPool.CACHED.submit(() -> {
|
||||||
List<IMessage> messages = new ArrayList<>();
|
List<MessagePackage> messagePackages = new ArrayList<>();
|
||||||
P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600);
|
P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600);
|
||||||
P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor();
|
P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor();
|
||||||
messageFramer.setListener(messageProcessor);
|
messageFramer.setListener(messageProcessor);
|
||||||
|
|
||||||
|
Channel empty = new Channel("Empty");
|
||||||
|
empty.setDecodeConfiguration(new DecodeConfigP25Phase1());
|
||||||
|
|
||||||
|
MessagePackager messagePackager = new MessagePackager();
|
||||||
|
|
||||||
|
//Setup a temporary event bus to capture channel start processing requests
|
||||||
|
EventBus eventBus = new EventBus("debug");
|
||||||
|
eventBus.register(messagePackager);
|
||||||
|
P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(empty);
|
||||||
|
trafficChannelManager.setInterModuleEventBus(eventBus);
|
||||||
|
|
||||||
|
//Register to receive events
|
||||||
|
trafficChannelManager.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent));
|
||||||
|
P25P1DecoderState decoderState = new P25P1DecoderState(empty, trafficChannelManager);
|
||||||
|
decoderState.setDecoderStateListener(decoderStateEvent -> messagePackager.add(decoderStateEvent));
|
||||||
|
decoderState.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent));
|
||||||
|
decoderState.start();
|
||||||
|
|
||||||
|
long frequency = getFrequencyFromFile(mLoadedFile.get());
|
||||||
|
|
||||||
|
if(frequency > 0)
|
||||||
|
{
|
||||||
|
trafficChannelManager.setCurrentControlFrequency(frequency, empty);
|
||||||
|
FrequencyConfigurationIdentifier id = FrequencyConfigurationIdentifier.create(frequency);
|
||||||
|
decoderState.getConfigurationIdentifierListener().receive(new IdentifierUpdateNotification(id,
|
||||||
|
IdentifierUpdateNotification.Operation.ADD, 1));
|
||||||
|
}
|
||||||
|
|
||||||
messageProcessor.setMessageListener(message -> {
|
messageProcessor.setMessageListener(message -> {
|
||||||
if(!(message instanceof StuffBitsMessage))
|
if(!(message instanceof StuffBitsMessage))
|
||||||
{
|
{
|
||||||
messages.add(message);
|
//Add the initial message to the packager so that it can be combined with any decoder state events.
|
||||||
|
messagePackager.add(message);
|
||||||
|
decoderState.receive(message);
|
||||||
|
|
||||||
|
//Collect the packaged message with events
|
||||||
|
messagePackages.add(messagePackager.getMessageWithEvents());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,13 +213,47 @@ public class P25P1Viewer extends VBox
|
|||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
getLoadingIndicator().setVisible(false);
|
getLoadingIndicator().setVisible(false);
|
||||||
getSelectedFileLabel().setText(file.getName());
|
getSelectedFileLabel().setText(file.getName());
|
||||||
mMessages.addAll(messages);
|
mMessagePackages.addAll(messagePackages);
|
||||||
getMessageTableView().scrollTo(0);
|
getMessagePackageTableView().scrollTo(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the channel frequency value from the bits file name to broadcast as the current frequency for each of
|
||||||
|
* the decoder states.
|
||||||
|
* @param file name to parse
|
||||||
|
* @return parsed frequency or zero.
|
||||||
|
*/
|
||||||
|
private static long getFrequencyFromFile(String file)
|
||||||
|
{
|
||||||
|
if(file == null || file.isEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(file.matches(FILE_FREQUENCY_REGEX))
|
||||||
|
{
|
||||||
|
Pattern p = Pattern.compile(FILE_FREQUENCY_REGEX);
|
||||||
|
Matcher m = p.matcher(file);
|
||||||
|
if(m.find())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String raw = m.group(1);
|
||||||
|
return Long.parseLong(raw);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't parse frequency from bits file [" + file + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the filter(s) applies to the list of messages
|
* Updates the filter(s) applies to the list of messages
|
||||||
*/
|
*/
|
||||||
@@ -180,12 +263,12 @@ public class P25P1Viewer extends VBox
|
|||||||
|
|
||||||
if(filterText != null && !filterText.isEmpty())
|
if(filterText != null && !filterText.isEmpty())
|
||||||
{
|
{
|
||||||
Predicate<IMessage> textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase());
|
Predicate<MessagePackage> textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase());
|
||||||
mFilteredMessages.setPredicate(textPredicate);
|
mFilteredMessagePackages.setPredicate(textPredicate);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mFilteredMessages.setPredicate(null);
|
mFilteredMessagePackages.setPredicate(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,12 +280,12 @@ public class P25P1Viewer extends VBox
|
|||||||
{
|
{
|
||||||
if(text != null && !text.isEmpty())
|
if(text != null && !text.isEmpty())
|
||||||
{
|
{
|
||||||
for(IMessage message: mFilteredMessages)
|
for(MessagePackage messagePackage: mFilteredMessagePackages)
|
||||||
{
|
{
|
||||||
if(message.toString().toLowerCase().contains(text.toLowerCase()))
|
if(messagePackage.toString().toLowerCase().contains(text.toLowerCase()))
|
||||||
{
|
{
|
||||||
getMessageTableView().getSelectionModel().select(message);
|
getMessagePackageTableView().getSelectionModel().select(messagePackage);
|
||||||
getMessageTableView().scrollTo(message);
|
getMessagePackageTableView().scrollTo(messagePackage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +300,7 @@ public class P25P1Viewer extends VBox
|
|||||||
{
|
{
|
||||||
if(text != null && !text.isEmpty())
|
if(text != null && !text.isEmpty())
|
||||||
{
|
{
|
||||||
IMessage selected = getMessageTableView().getSelectionModel().getSelectedItem();
|
MessagePackage selected = getMessagePackageTableView().getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
if(selected == null)
|
if(selected == null)
|
||||||
{
|
{
|
||||||
@@ -225,18 +308,18 @@ public class P25P1Viewer extends VBox
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int row = mFilteredMessages.indexOf(selected);
|
int row = mFilteredMessagePackages.indexOf(selected);
|
||||||
|
|
||||||
for(int x = row + 1; x < mFilteredMessages.size(); x++)
|
for(int x = row + 1; x < mFilteredMessagePackages.size(); x++)
|
||||||
{
|
{
|
||||||
if(x < mFilteredMessages.size())
|
if(x < mFilteredMessagePackages.size())
|
||||||
{
|
{
|
||||||
IMessage message = mFilteredMessages.get(x);
|
MessagePackage messagePackage = mFilteredMessagePackages.get(x);
|
||||||
|
|
||||||
if(message.toString().toLowerCase().contains(text.toLowerCase()))
|
if(messagePackage.toString().toLowerCase().contains(text.toLowerCase()))
|
||||||
{
|
{
|
||||||
getMessageTableView().getSelectionModel().select(message);
|
getMessagePackageTableView().getSelectionModel().select(messagePackage);
|
||||||
getMessageTableView().scrollTo(message);
|
getMessagePackageTableView().scrollTo(messagePackage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,25 +327,40 @@ public class P25P1Viewer extends VBox
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private MessagePackageViewer getMessagePackageViewer()
|
||||||
* List view control with DMR messages
|
|
||||||
*/
|
|
||||||
private TableView<IMessage> getMessageTableView()
|
|
||||||
{
|
{
|
||||||
if(mMessageTableView == null)
|
if(mMessagePackageViewer == null)
|
||||||
{
|
{
|
||||||
mMessageTableView = new TableView<>();
|
mMessagePackageViewer = new MessagePackageViewer();
|
||||||
mMessageTableView.setPlaceholder(getLoadingIndicator());
|
mMessagePackageViewer.setMaxWidth(Double.MAX_VALUE);
|
||||||
SortedList<IMessage> sortedList = new SortedList<>(mFilteredMessages);
|
|
||||||
sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty());
|
|
||||||
mMessageTableView.setItems(sortedList);
|
|
||||||
|
|
||||||
mMessageTableView.setOnKeyPressed(event ->
|
//Register for table selection events to display the selected value.
|
||||||
|
getMessagePackageTableView().getSelectionModel().selectedItemProperty()
|
||||||
|
.addListener((observable, oldValue, newValue) -> getMessagePackageViewer().set(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMessagePackageViewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List view control with messages
|
||||||
|
*/
|
||||||
|
private TableView<MessagePackage> getMessagePackageTableView()
|
||||||
|
{
|
||||||
|
if(mMessagePackageTableView == null)
|
||||||
|
{
|
||||||
|
mMessagePackageTableView = new TableView<>();
|
||||||
|
mMessagePackageTableView.setPlaceholder(getLoadingIndicator());
|
||||||
|
SortedList<MessagePackage> sortedList = new SortedList<>(mFilteredMessagePackages);
|
||||||
|
sortedList.comparatorProperty().bind(mMessagePackageTableView.comparatorProperty());
|
||||||
|
mMessagePackageTableView.setItems(sortedList);
|
||||||
|
|
||||||
|
mMessagePackageTableView.setOnKeyPressed(event ->
|
||||||
{
|
{
|
||||||
if(KEY_CODE_COPY.match(event))
|
if(KEY_CODE_COPY.match(event))
|
||||||
{
|
{
|
||||||
final Set<Integer> rows = new TreeSet<>();
|
final Set<Integer> rows = new TreeSet<>();
|
||||||
for (final TablePosition tablePosition : mMessageTableView.getSelectionModel().getSelectedCells())
|
for (final TablePosition tablePosition : mMessagePackageTableView.getSelectionModel().getSelectedCells())
|
||||||
{
|
{
|
||||||
rows.add(tablePosition.getRow());
|
rows.add(tablePosition.getRow());
|
||||||
}
|
}
|
||||||
@@ -282,7 +380,7 @@ public class P25P1Viewer extends VBox
|
|||||||
|
|
||||||
boolean firstCol = true;
|
boolean firstCol = true;
|
||||||
|
|
||||||
for (final TableColumn<?, ?> column : mMessageTableView.getColumns())
|
for (final TableColumn<?, ?> column : mMessagePackageTableView.getColumns())
|
||||||
{
|
{
|
||||||
if(firstCol)
|
if(firstCol)
|
||||||
{
|
{
|
||||||
@@ -313,23 +411,44 @@ public class P25P1Viewer extends VBox
|
|||||||
validColumn.setText("Valid");
|
validColumn.setText("Valid");
|
||||||
validColumn.setCellValueFactory(new PropertyValueFactory<>("valid"));
|
validColumn.setCellValueFactory(new PropertyValueFactory<>("valid"));
|
||||||
|
|
||||||
|
TableColumn timeslotColumn = new TableColumn();
|
||||||
|
timeslotColumn.setPrefWidth(35);
|
||||||
|
timeslotColumn.setText("TS");
|
||||||
|
timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot"));
|
||||||
|
|
||||||
TableColumn messageColumn = new TableColumn();
|
TableColumn messageColumn = new TableColumn();
|
||||||
messageColumn.setPrefWidth(1000);
|
messageColumn.setPrefWidth(900);
|
||||||
messageColumn.setText("Message");
|
messageColumn.setText("Message");
|
||||||
messageColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures, ObservableValue>) param -> {
|
messageColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures, ObservableValue>) param -> {
|
||||||
SimpleStringProperty property = new SimpleStringProperty();
|
SimpleStringProperty property = new SimpleStringProperty();
|
||||||
if(param.getValue() instanceof IMessage message)
|
if(param.getValue() instanceof MessagePackage messagePackage)
|
||||||
{
|
{
|
||||||
property.set(message.toString());
|
property.set(messagePackage.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return property;
|
return property;
|
||||||
});
|
});
|
||||||
|
|
||||||
mMessageTableView.getColumns().addAll(timestampColumn, validColumn, messageColumn);
|
TableColumn decodeEventCountColumn = new TableColumn();
|
||||||
|
decodeEventCountColumn.setPrefWidth(50);
|
||||||
|
decodeEventCountColumn.setText("Events");
|
||||||
|
decodeEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decodeEventCount"));
|
||||||
|
|
||||||
|
TableColumn decoderStateEventCountColumn = new TableColumn();
|
||||||
|
decoderStateEventCountColumn.setPrefWidth(50);
|
||||||
|
decoderStateEventCountColumn.setText("States");
|
||||||
|
decoderStateEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decoderStateEventCount"));
|
||||||
|
|
||||||
|
TableColumn channelStartCountColumn = new TableColumn();
|
||||||
|
channelStartCountColumn.setPrefWidth(50);
|
||||||
|
channelStartCountColumn.setText("Starts");
|
||||||
|
channelStartCountColumn.setCellValueFactory(new PropertyValueFactory<>("channelStartProcessingRequestCount"));
|
||||||
|
|
||||||
|
mMessagePackageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn,
|
||||||
|
decodeEventCountColumn, decoderStateEventCountColumn, channelStartCountColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mMessageTableView;
|
return mMessagePackageTableView;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -343,7 +462,7 @@ public class P25P1Viewer extends VBox
|
|||||||
mSelectFileButton = new Button("Select ...");
|
mSelectFileButton = new Button("Select ...");
|
||||||
mSelectFileButton.onActionProperty().set(event -> {
|
mSelectFileButton.onActionProperty().set(event -> {
|
||||||
FileChooser fileChooser = new FileChooser();
|
FileChooser fileChooser = new FileChooser();
|
||||||
fileChooser.setTitle("Select DMR .bits Recording");
|
fileChooser.setTitle("Select P25 Phase 1 .bits Recording");
|
||||||
String lastDirectory = mPreferences.get(LAST_SELECTED_DIRECTORY, null);
|
String lastDirectory = mPreferences.get(LAST_SELECTED_DIRECTORY, null);
|
||||||
if(lastDirectory != null)
|
if(lastDirectory != null)
|
||||||
{
|
{
|
||||||
|
|||||||
738
src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java
Normal file
738
src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java
Normal file
@@ -0,0 +1,738 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.viewer;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.EventBus;
|
||||||
|
import io.github.dsheirer.controller.channel.Channel;
|
||||||
|
import io.github.dsheirer.gui.control.IntegerTextField;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
|
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroupManager;
|
||||||
|
import io.github.dsheirer.message.StuffBitsMessage;
|
||||||
|
import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent;
|
||||||
|
import io.github.dsheirer.module.decode.p25.P25TrafficChannelManager;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25FrequencyBand;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.P25P2MessageFramer;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.P25P2MessageProcessor;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
|
||||||
|
import io.github.dsheirer.record.binary.BinaryReader;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.collections.transformation.FilteredList;
|
||||||
|
import javafx.collections.transformation.SortedList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TablePosition;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APCO25 Phase 2 viewer panel
|
||||||
|
*/
|
||||||
|
public class P25P2Viewer extends VBox
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(P25P2Viewer.class);
|
||||||
|
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
|
||||||
|
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p2";
|
||||||
|
private static final String FILE_FREQUENCY_REGEX = ".*\\d{8}_\\d{6}_(\\d{9}).*";
|
||||||
|
private Preferences mPreferences = Preferences.userNodeForPackage(P25P2Viewer.class);
|
||||||
|
private Button mSelectFileButton;
|
||||||
|
private Label mSelectedFileLabel;
|
||||||
|
private TableView<MessagePackage> mMessagePackageTableView;
|
||||||
|
private ObservableList<MessagePackage> mMessagePackages = FXCollections.observableArrayList();
|
||||||
|
private FilteredList<MessagePackage> mFilteredMessagePackages = new FilteredList<>(mMessagePackages);
|
||||||
|
private CheckBox mShowTS0;
|
||||||
|
private CheckBox mShowTS1;
|
||||||
|
private CheckBox mShowTS2;
|
||||||
|
private TextField mSearchText;
|
||||||
|
private TextField mFindText;
|
||||||
|
private Button mFindButton;
|
||||||
|
private Button mFindNextButton;
|
||||||
|
private ProgressIndicator mLoadingIndicator;
|
||||||
|
private IntegerTextField mWACNTextField;
|
||||||
|
private IntegerTextField mSystemTextField;
|
||||||
|
private IntegerTextField mNACTextField;
|
||||||
|
private Button mReloadButton;
|
||||||
|
private StringProperty mLoadedFile = new SimpleStringProperty();
|
||||||
|
private MessagePackageViewer mMessagePackageViewer;
|
||||||
|
|
||||||
|
public P25P2Viewer()
|
||||||
|
{
|
||||||
|
setPadding(new Insets(5));
|
||||||
|
setSpacing(5);
|
||||||
|
|
||||||
|
HBox fileBox = new HBox();
|
||||||
|
fileBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
fileBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
fileBox.setSpacing(5);
|
||||||
|
getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER);
|
||||||
|
|
||||||
|
HBox.setHgrow(getSelectFileButton(), Priority.NEVER);
|
||||||
|
HBox.setHgrow(getSelectedFileLabel(), Priority.ALWAYS);
|
||||||
|
fileBox.getChildren().addAll(getSelectFileButton(), getSelectedFileLabel());
|
||||||
|
|
||||||
|
HBox scrambleSettingsBox = new HBox();
|
||||||
|
scrambleSettingsBox.setAlignment(Pos.BASELINE_LEFT);
|
||||||
|
scrambleSettingsBox.setSpacing(5);
|
||||||
|
Label wacnLabel = new Label("WACN:");
|
||||||
|
Label systemLabel = new Label("SYSTEM:");
|
||||||
|
Label nacLabel = new Label("NAC:");
|
||||||
|
scrambleSettingsBox.getChildren().addAll(wacnLabel, getWACNTextField(), systemLabel, getSystemTextField(),
|
||||||
|
nacLabel, getNACTextField(), getReloadButton());
|
||||||
|
|
||||||
|
HBox filterBox = new HBox();
|
||||||
|
filterBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
filterBox.setAlignment(Pos.BASELINE_CENTER);
|
||||||
|
filterBox.setSpacing(5);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Label showLabel = new Label("Show:");
|
||||||
|
HBox.setMargin(showLabel, new Insets(0,0,0,15));
|
||||||
|
|
||||||
|
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(scrambleSettingsBox, Priority.NEVER);
|
||||||
|
VBox.setVgrow(getMessagePackageTableView(), Priority.ALWAYS);
|
||||||
|
VBox.setVgrow(getMessagePackageViewer(), Priority.NEVER);
|
||||||
|
|
||||||
|
getChildren().addAll(fileBox, scrambleSettingsBox, filterBox, getMessagePackageTableView(), getMessagePackageViewer());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the channel frequency value from the bits file name to broadcast as the current frequency for each of
|
||||||
|
* the decoder states.
|
||||||
|
* @param file name to parse
|
||||||
|
* @return parsed frequency or zero.
|
||||||
|
*/
|
||||||
|
private static long getFrequencyFromFile(String file)
|
||||||
|
{
|
||||||
|
if(file == null || file.isEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(file.matches(FILE_FREQUENCY_REGEX))
|
||||||
|
{
|
||||||
|
Pattern p = Pattern.compile(FILE_FREQUENCY_REGEX);
|
||||||
|
Matcher m = p.matcher(file);
|
||||||
|
if(m.find())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String raw = m.group(1);
|
||||||
|
return Long.parseLong(raw);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't parse frequency from bits file [" + file + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the recording file and loads the content into the viewer
|
||||||
|
*
|
||||||
|
* Note: invoke this method off of the UI thread in a thread pool executor and the results will be loaded into the
|
||||||
|
* message table back on the JavaFX UI thread.
|
||||||
|
*
|
||||||
|
* @param file containing a .bits recording of decoded data.
|
||||||
|
*/
|
||||||
|
private void load(File file)
|
||||||
|
{
|
||||||
|
mLog.info("Loading File: " + file);
|
||||||
|
if(file != null && file.exists())
|
||||||
|
{
|
||||||
|
mLoadedFile.set(file.toString());
|
||||||
|
mMessagePackages.clear();
|
||||||
|
getLoadingIndicator().setVisible(true);
|
||||||
|
getSelectedFileLabel().setText("Loading ...");
|
||||||
|
|
||||||
|
int wacn = getWACNTextField().get();
|
||||||
|
int system = getSystemTextField().get();
|
||||||
|
int nac = getNACTextField().get();
|
||||||
|
ScrambleParameters scrambleParameters = new ScrambleParameters(wacn, system, nac);
|
||||||
|
|
||||||
|
ThreadPool.CACHED.submit(() -> {
|
||||||
|
List<MessagePackage> messages = new ArrayList<>();
|
||||||
|
P25P2MessageFramer messageFramer = new P25P2MessageFramer(null, 9600);
|
||||||
|
messageFramer.setScrambleParameters(scrambleParameters);
|
||||||
|
P25P2MessageProcessor messageProcessor = new P25P2MessageProcessor();
|
||||||
|
messageFramer.setListener(messageProcessor);
|
||||||
|
Channel empty = new Channel("Empty");
|
||||||
|
empty.setDecodeConfiguration(new DecodeConfigP25Phase2());
|
||||||
|
|
||||||
|
MessagePackager messagePackager = new MessagePackager();
|
||||||
|
|
||||||
|
//Setup a temporary event bus to capture channel start processing requests
|
||||||
|
EventBus eventBus = new EventBus("debug");
|
||||||
|
eventBus.register(messagePackager);
|
||||||
|
P25TrafficChannelManager trafficChannelManager = new P25TrafficChannelManager(empty);
|
||||||
|
trafficChannelManager.setInterModuleEventBus(eventBus);
|
||||||
|
|
||||||
|
//Register to receive events
|
||||||
|
trafficChannelManager.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent));
|
||||||
|
PatchGroupManager patchGroupManager = new PatchGroupManager();
|
||||||
|
P25P2DecoderState decoderState1 = new P25P2DecoderState(empty, 1, trafficChannelManager,
|
||||||
|
patchGroupManager);
|
||||||
|
decoderState1.setDecoderStateListener(decoderStateEvent -> messagePackager.add(decoderStateEvent));
|
||||||
|
decoderState1.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent));
|
||||||
|
P25P2DecoderState decoderState2 = new P25P2DecoderState(empty, 2, trafficChannelManager,
|
||||||
|
patchGroupManager);
|
||||||
|
decoderState2.setDecoderStateListener(decoderStateEvent -> messagePackager.add(decoderStateEvent));
|
||||||
|
decoderState2.addDecodeEventListener(decodeEvent -> messagePackager.add(decodeEvent));
|
||||||
|
decoderState1.start();
|
||||||
|
decoderState2.start();
|
||||||
|
|
||||||
|
long frequency = getFrequencyFromFile(mLoadedFile.get());
|
||||||
|
|
||||||
|
if(frequency > 0)
|
||||||
|
{
|
||||||
|
trafficChannelManager.setCurrentControlFrequency(frequency, empty);
|
||||||
|
FrequencyConfigurationIdentifier id = FrequencyConfigurationIdentifier.create(frequency);
|
||||||
|
decoderState1.getConfigurationIdentifierListener().receive(new IdentifierUpdateNotification(id,
|
||||||
|
IdentifierUpdateNotification.Operation.ADD, 1));
|
||||||
|
decoderState2.getConfigurationIdentifierListener().receive(new IdentifierUpdateNotification(id,
|
||||||
|
IdentifierUpdateNotification.Operation.ADD, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: testing use - PCWIN TDMA Frequency Band as preload data
|
||||||
|
//ID:2 OFFSET:-45000000 SPACING:12500 BASE:851012500 TDMA BW:12500 TIMESLOTS:2 VOCODER:HALF_RATE
|
||||||
|
P25FrequencyBand band = new P25FrequencyBand(2, 851012500l, -45000000l, 12500, 12500, 2);
|
||||||
|
P25FrequencyBandPreloadDataContent content = new P25FrequencyBandPreloadDataContent(Collections.singleton(band));
|
||||||
|
messageProcessor.preload(content);
|
||||||
|
|
||||||
|
messageProcessor.setMessageListener(message -> {
|
||||||
|
if(!(message instanceof StuffBitsMessage))
|
||||||
|
{
|
||||||
|
// System.out.println(message);
|
||||||
|
//Add the initial message to the packager so that it can be combined with any decoder state events.
|
||||||
|
messagePackager.add(message);
|
||||||
|
if(message.getTimeslot() == P25P1Message.TIMESLOT_1)
|
||||||
|
{
|
||||||
|
decoderState1.receive(message);
|
||||||
|
}
|
||||||
|
else if(message.getTimeslot() == P25P1Message.TIMESLOT_2)
|
||||||
|
{
|
||||||
|
decoderState2.receive(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Collect the packaged message with events
|
||||||
|
messages.add(messagePackager.getMessageWithEvents());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
|
||||||
|
{
|
||||||
|
while(reader.hasNext())
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = reader.next();
|
||||||
|
// System.out.println("Processing Bytes " + buffer.capacity() + " / " + reader.getByteCounter());
|
||||||
|
messageFramer.receive(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception ioe)
|
||||||
|
{
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
getLoadingIndicator().setVisible(false);
|
||||||
|
getSelectedFileLabel().setText(file.getName());
|
||||||
|
mMessagePackages.addAll(messages);
|
||||||
|
getMessagePackageTableView().scrollTo(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("Can't load file: " + file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the filter(s) applies to the list of messages
|
||||||
|
*/
|
||||||
|
private void updateFilters()
|
||||||
|
{
|
||||||
|
Predicate<MessagePackage> 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())
|
||||||
|
{
|
||||||
|
mFilteredMessagePackages.setPredicate(timeslotPredicate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Predicate<MessagePackage> textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase());
|
||||||
|
mFilteredMessagePackages.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(MessagePackage messagePackage: mFilteredMessagePackages)
|
||||||
|
{
|
||||||
|
if(messagePackage.toString().toLowerCase().contains(text.toLowerCase()))
|
||||||
|
{
|
||||||
|
getMessagePackageTableView().getSelectionModel().select(messagePackage);
|
||||||
|
getMessagePackageTableView().scrollTo(messagePackage);
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
MessagePackage selected = getMessagePackageTableView().getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
|
if(selected == null)
|
||||||
|
{
|
||||||
|
find(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int row = mFilteredMessagePackages.indexOf(selected);
|
||||||
|
|
||||||
|
for(int x = row + 1; x < mFilteredMessagePackages.size(); x++)
|
||||||
|
{
|
||||||
|
if(x < mFilteredMessagePackages.size())
|
||||||
|
{
|
||||||
|
MessagePackage messagePackage = mFilteredMessagePackages.get(x);
|
||||||
|
|
||||||
|
if(messagePackage.toString().toLowerCase().contains(text.toLowerCase()))
|
||||||
|
{
|
||||||
|
getMessagePackageTableView().getSelectionModel().select(messagePackage);
|
||||||
|
getMessagePackageTableView().scrollTo(messagePackage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessagePackageViewer getMessagePackageViewer()
|
||||||
|
{
|
||||||
|
if(mMessagePackageViewer == null)
|
||||||
|
{
|
||||||
|
mMessagePackageViewer = new MessagePackageViewer();
|
||||||
|
mMessagePackageViewer.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
|
//Register for table selection events to display the selected value.
|
||||||
|
getMessagePackageTableView().getSelectionModel().selectedItemProperty()
|
||||||
|
.addListener((observable, oldValue, newValue) -> getMessagePackageViewer().set(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMessagePackageViewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegerTextField getWACNTextField()
|
||||||
|
{
|
||||||
|
if(mWACNTextField == null)
|
||||||
|
{
|
||||||
|
mWACNTextField = new IntegerTextField();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mWACNTextField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegerTextField getSystemTextField()
|
||||||
|
{
|
||||||
|
if(mSystemTextField == null)
|
||||||
|
{
|
||||||
|
mSystemTextField = new IntegerTextField();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSystemTextField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegerTextField getNACTextField()
|
||||||
|
{
|
||||||
|
if(mNACTextField == null)
|
||||||
|
{
|
||||||
|
mNACTextField = new IntegerTextField();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mNACTextField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button getReloadButton()
|
||||||
|
{
|
||||||
|
if(mReloadButton == null)
|
||||||
|
{
|
||||||
|
mReloadButton = new Button("Reload");
|
||||||
|
mReloadButton.disableProperty().bind(Bindings.isNull(mLoadedFile));
|
||||||
|
mReloadButton.setOnAction(event -> load(new File(mLoadedFile.get())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mReloadButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List view control with message packages
|
||||||
|
*/
|
||||||
|
private TableView<MessagePackage> getMessagePackageTableView()
|
||||||
|
{
|
||||||
|
if(mMessagePackageTableView == null)
|
||||||
|
{
|
||||||
|
mMessagePackageTableView = new TableView<>();
|
||||||
|
mMessagePackageTableView.setPlaceholder(getLoadingIndicator());
|
||||||
|
SortedList<MessagePackage> sortedList = new SortedList<>(mFilteredMessagePackages);
|
||||||
|
sortedList.comparatorProperty().bind(mMessagePackageTableView.comparatorProperty());
|
||||||
|
mMessagePackageTableView.setItems(sortedList);
|
||||||
|
|
||||||
|
mMessagePackageTableView.setOnKeyPressed(event ->
|
||||||
|
{
|
||||||
|
if(KEY_CODE_COPY.match(event))
|
||||||
|
{
|
||||||
|
final Set<Integer> rows = new TreeSet<>();
|
||||||
|
for (final TablePosition tablePosition : mMessagePackageTableView.getSelectionModel().getSelectedCells())
|
||||||
|
{
|
||||||
|
rows.add(tablePosition.getRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
boolean firstRow = true;
|
||||||
|
for (final Integer row : rows)
|
||||||
|
{
|
||||||
|
if(firstRow)
|
||||||
|
{
|
||||||
|
firstRow = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean firstCol = true;
|
||||||
|
|
||||||
|
for (final TableColumn<?, ?> column : mMessagePackageTableView.getColumns())
|
||||||
|
{
|
||||||
|
if(firstCol)
|
||||||
|
{
|
||||||
|
firstCol = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append('\t');
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object cellData = column.getCellData(row);
|
||||||
|
sb.append(cellData == null ? "" : cellData.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ClipboardContent clipboardContent = new ClipboardContent();
|
||||||
|
clipboardContent.putString(sb.toString());
|
||||||
|
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TableColumn timestampColumn = new TableColumn();
|
||||||
|
timestampColumn.setPrefWidth(110);
|
||||||
|
timestampColumn.setText("Time");
|
||||||
|
timestampColumn.setCellValueFactory(new PropertyValueFactory<>("timestamp"));
|
||||||
|
|
||||||
|
TableColumn validColumn = new TableColumn();
|
||||||
|
validColumn.setPrefWidth(50);
|
||||||
|
validColumn.setText("Valid");
|
||||||
|
validColumn.setCellValueFactory(new PropertyValueFactory<>("valid"));
|
||||||
|
|
||||||
|
TableColumn timeslotColumn = new TableColumn();
|
||||||
|
timeslotColumn.setPrefWidth(35);
|
||||||
|
timeslotColumn.setText("TS");
|
||||||
|
timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot"));
|
||||||
|
|
||||||
|
TableColumn messageColumn = new TableColumn();
|
||||||
|
messageColumn.setPrefWidth(900);
|
||||||
|
messageColumn.setText("Message");
|
||||||
|
messageColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures, ObservableValue>) param -> {
|
||||||
|
SimpleStringProperty property = new SimpleStringProperty();
|
||||||
|
if(param.getValue() instanceof MessagePackage messagePackage)
|
||||||
|
{
|
||||||
|
property.set(messagePackage.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return property;
|
||||||
|
});
|
||||||
|
|
||||||
|
TableColumn decodeEventCountColumn = new TableColumn();
|
||||||
|
decodeEventCountColumn.setPrefWidth(50);
|
||||||
|
decodeEventCountColumn.setText("Events");
|
||||||
|
decodeEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decodeEventCount"));
|
||||||
|
|
||||||
|
TableColumn decoderStateEventCountColumn = new TableColumn();
|
||||||
|
decoderStateEventCountColumn.setPrefWidth(50);
|
||||||
|
decoderStateEventCountColumn.setText("States");
|
||||||
|
decoderStateEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decoderStateEventCount"));
|
||||||
|
|
||||||
|
TableColumn channelStartCountColumn = new TableColumn();
|
||||||
|
channelStartCountColumn.setPrefWidth(50);
|
||||||
|
channelStartCountColumn.setText("Starts");
|
||||||
|
channelStartCountColumn.setCellValueFactory(new PropertyValueFactory<>("channelStartProcessingRequestCount"));
|
||||||
|
|
||||||
|
mMessagePackageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn,
|
||||||
|
decodeEventCountColumn, decoderStateEventCountColumn, channelStartCountColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMessagePackageTableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 P25 Phase 2 .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"));
|
||||||
|
final File selected = fileChooser.showOpenDialog(getScene().getWindow());
|
||||||
|
|
||||||
|
if(selected != null)
|
||||||
|
{
|
||||||
|
mPreferences.put(LAST_SELECTED_DIRECTORY, selected.getParent());
|
||||||
|
load(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSelectFileButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spinny loading icon to show over the message table view
|
||||||
|
*/
|
||||||
|
private ProgressIndicator getLoadingIndicator()
|
||||||
|
{
|
||||||
|
if(mLoadingIndicator == null)
|
||||||
|
{
|
||||||
|
mLoadingIndicator = new ProgressIndicator();
|
||||||
|
mLoadingIndicator.setProgress(-1);
|
||||||
|
mLoadingIndicator.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLoadingIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.setOnKeyPressed(event -> {
|
||||||
|
if(event.getCode().equals(KeyCode.ENTER))
|
||||||
|
{
|
||||||
|
getFindButton().fire();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.identifier;
|
package io.github.dsheirer.identifier;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifier form. Indicates the type of identifier.
|
* Identifier form. Indicates the type of identifier.
|
||||||
*/
|
*/
|
||||||
@@ -64,4 +66,21 @@ public enum Form
|
|||||||
UNIQUE_ID,
|
UNIQUE_ID,
|
||||||
WACN,
|
WACN,
|
||||||
ANY;
|
ANY;
|
||||||
|
|
||||||
|
Form()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity forms that are used to identify entities in P25 call events.
|
||||||
|
*/
|
||||||
|
public static EnumSet<Form> ENTITY_FORMS = EnumSet.of(Form.RADIO, Form.TALKGROUP, Form.PATCH_GROUP, Form.TELEPHONE_NUMBER);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this form is a form that can be used to identify an entity
|
||||||
|
*/
|
||||||
|
public boolean isEntityForm()
|
||||||
|
{
|
||||||
|
return ENTITY_FORMS.contains(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,12 @@ public enum IdentifierClass
|
|||||||
//Communication network identifier
|
//Communication network identifier
|
||||||
NETWORK,
|
NETWORK,
|
||||||
|
|
||||||
|
//User network address
|
||||||
|
USER_NETWORK_ADDRESS,
|
||||||
|
|
||||||
|
//User network port
|
||||||
|
USER_NETWORK_PORT,
|
||||||
|
|
||||||
//Communication user identifier
|
//Communication user identifier
|
||||||
USER;
|
USER;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,14 +20,13 @@
|
|||||||
package io.github.dsheirer.identifier;
|
package io.github.dsheirer.identifier;
|
||||||
|
|
||||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (Immutable) Collection of identifiers with convenient accessor methods
|
* (Immutable) Collection of identifiers with convenient accessor methods
|
||||||
@@ -274,43 +273,90 @@ public class IdentifierCollection
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first identifier in this collection that is assigned a FROM role
|
* Indicates if this collection has an identifier that matches the specified identiifer.
|
||||||
|
* @param toCheck identifier
|
||||||
|
* @return true if the identifier already exists in the collection.
|
||||||
|
*/
|
||||||
|
public boolean hasIdentifier(Identifier toCheck)
|
||||||
|
{
|
||||||
|
if(toCheck == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Identifier identifier : mIdentifiers)
|
||||||
|
{
|
||||||
|
if(identifier.equals(toCheck))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first entity identifier in this collection that is assigned a FROM role
|
||||||
*/
|
*/
|
||||||
public Identifier getFromIdentifier()
|
public Identifier getFromIdentifier()
|
||||||
{
|
{
|
||||||
Identifier from = getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM);
|
Identifier from = getIdentifier(IdentifierClass.USER, Form.RADIO, Role.FROM);
|
||||||
|
|
||||||
if(from == null)
|
if(from != null)
|
||||||
{
|
{
|
||||||
List<Identifier> fromIdentifiers = getIdentifiers(Role.FROM);
|
return from;
|
||||||
|
|
||||||
if(!fromIdentifiers.isEmpty())
|
|
||||||
{
|
|
||||||
from = fromIdentifiers.get(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return from;
|
List<Identifier> identifiers = getIdentifiers(Form.TELEPHONE_NUMBER);
|
||||||
|
|
||||||
|
if(!identifiers.isEmpty())
|
||||||
|
{
|
||||||
|
return identifiers.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Identifier> fromIdentifiers = getIdentifiers(Role.FROM);
|
||||||
|
|
||||||
|
if(!fromIdentifiers.isEmpty())
|
||||||
|
{
|
||||||
|
return fromIdentifiers.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first identifier in this collection that is assigned a TO role
|
* Returns the first entity identifier in this collection that is assigned a TO role
|
||||||
*/
|
*/
|
||||||
public Identifier getToIdentifier()
|
public Identifier getToIdentifier()
|
||||||
{
|
{
|
||||||
Identifier to = getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO);
|
Identifier to = getIdentifier(IdentifierClass.USER, Form.PATCH_GROUP, Role.TO);
|
||||||
|
|
||||||
if(to == null)
|
if(to != null)
|
||||||
{
|
{
|
||||||
to = getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO);
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(to == null)
|
to = getIdentifier(IdentifierClass.USER, Form.TALKGROUP, Role.TO);
|
||||||
|
|
||||||
|
if(to != null)
|
||||||
{
|
{
|
||||||
List<Identifier> toIdentifiers = getIdentifiers(Role.TO);
|
return to;
|
||||||
if(!toIdentifiers.isEmpty())
|
}
|
||||||
|
|
||||||
|
to = getIdentifier(IdentifierClass.USER, Form.RADIO, Role.TO);
|
||||||
|
|
||||||
|
if(to != null)
|
||||||
|
{
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Identifier> toIdentifiers = getIdentifiers(Role.TO);
|
||||||
|
|
||||||
|
for(Identifier identifier: toIdentifiers)
|
||||||
|
{
|
||||||
|
if(identifier.getForm() != Form.ENCRYPTION_KEY)
|
||||||
{
|
{
|
||||||
to = toIdentifiers.get(0);
|
return identifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ public abstract class IntegerIdentifier extends Identifier<Integer>
|
|||||||
super(value, identifierClass, form, role);
|
super(value, identifierClass, form, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o)
|
public boolean equals(Object o)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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.identifier.ipv4;
|
package io.github.dsheirer.identifier.ipv4;
|
||||||
@@ -29,6 +28,6 @@ public abstract class IPV4Identifier extends Identifier<IPV4Address>
|
|||||||
{
|
{
|
||||||
public IPV4Identifier(IPV4Address value, Role role)
|
public IPV4Identifier(IPV4Address value, Role role)
|
||||||
{
|
{
|
||||||
super(value, IdentifierClass.USER, Form.IPV4_ADDRESS, role);
|
super(value, IdentifierClass.USER_NETWORK_ADDRESS, Form.IPV4_ADDRESS, role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -95,25 +95,44 @@ public class PatchGroup
|
|||||||
/**
|
/**
|
||||||
* Adds the talkgroup identifier to this patched group.
|
* Adds the talkgroup identifier to this patched group.
|
||||||
* @param patchedGroupIdentifier to add
|
* @param patchedGroupIdentifier to add
|
||||||
|
* @return true if the patched group identifier is new and was added
|
||||||
*/
|
*/
|
||||||
public void addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier)
|
public boolean addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier)
|
||||||
{
|
{
|
||||||
|
boolean added = false;
|
||||||
|
|
||||||
if(!mPatchedTalkgroupIdentifiers.contains(patchedGroupIdentifier))
|
if(!mPatchedTalkgroupIdentifiers.contains(patchedGroupIdentifier))
|
||||||
{
|
{
|
||||||
mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier);
|
added |= mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the patched talkgroup from this patch group.
|
||||||
|
* @param talkgroup to remove
|
||||||
|
*/
|
||||||
|
public void removePatchedTalkgroup(TalkgroupIdentifier talkgroup)
|
||||||
|
{
|
||||||
|
mPatchedTalkgroupIdentifiers.remove(talkgroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a list of talkgroup identifiers to this patched group.
|
* Adds a list of talkgroup identifiers to this patched group.
|
||||||
* @param patchedGroupIdentifiers to add
|
* @param patchedGroupIdentifiers to add
|
||||||
*/
|
*/
|
||||||
public void addPatchedTalkgroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
|
public boolean addPatchedTalkgroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
|
||||||
{
|
{
|
||||||
|
boolean added = false;
|
||||||
|
|
||||||
for(TalkgroupIdentifier identifier : patchedGroupIdentifiers)
|
for(TalkgroupIdentifier identifier : patchedGroupIdentifiers)
|
||||||
{
|
{
|
||||||
addPatchedTalkgroup(identifier);
|
added |= addPatchedTalkgroup(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,24 +148,41 @@ public class PatchGroup
|
|||||||
* Adds the radio identifier to this patched group.
|
* Adds the radio identifier to this patched group.
|
||||||
* @param patchedRadioIdentifier to add
|
* @param patchedRadioIdentifier to add
|
||||||
*/
|
*/
|
||||||
public void addPatchedRadio(RadioIdentifier patchedRadioIdentifier)
|
public boolean addPatchedRadio(RadioIdentifier patchedRadioIdentifier)
|
||||||
{
|
{
|
||||||
if(!mPatchedRadioIdentifiers.contains(patchedRadioIdentifier))
|
if(!mPatchedRadioIdentifiers.contains(patchedRadioIdentifier))
|
||||||
{
|
{
|
||||||
mPatchedRadioIdentifiers.add(patchedRadioIdentifier);
|
mPatchedRadioIdentifiers.add(patchedRadioIdentifier);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the patched radio from this patch group.
|
||||||
|
* @param radioIdentifier to remove
|
||||||
|
*/
|
||||||
|
public void removePatchedRadio(RadioIdentifier radioIdentifier)
|
||||||
|
{
|
||||||
|
mPatchedRadioIdentifiers.remove(radioIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a list of radio identifiers to this patched group.
|
* Adds a list of radio identifiers to this patched group.
|
||||||
* @param patchedRadioIdentifiers to add
|
* @param patchedRadioIdentifiers to add
|
||||||
|
* @return true if any new identifiers were added
|
||||||
*/
|
*/
|
||||||
public void addPatchedRadios(List<RadioIdentifier> patchedRadioIdentifiers)
|
public boolean addPatchedRadios(List<RadioIdentifier> patchedRadioIdentifiers)
|
||||||
{
|
{
|
||||||
|
boolean added = false;
|
||||||
|
|
||||||
for(RadioIdentifier identifier : patchedRadioIdentifiers)
|
for(RadioIdentifier identifier : patchedRadioIdentifiers)
|
||||||
{
|
{
|
||||||
addPatchedRadio(identifier);
|
added |= addPatchedRadio(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,11 +22,14 @@ package io.github.dsheirer.identifier.patch;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
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.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager for (temporary) patch groups aka super groups. This manager monitors patch group additions and deletions and
|
* Manager for (temporary) patch groups aka super groups. This manager monitors patch group additions and deletions and
|
||||||
@@ -39,7 +42,8 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class PatchGroupManager
|
public class PatchGroupManager
|
||||||
{
|
{
|
||||||
private Map<Integer,PatchGroupIdentifier> mPatchGroupMap = new HashMap<>();
|
private static final long PATCH_GROUP_FRESHNESS_THRESHOLD_MS = Duration.ofSeconds(30).toMillis();
|
||||||
|
private Map<Integer,PatchGroupTracker> mPatchGroupTrackerMap = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -48,12 +52,36 @@ public class PatchGroupManager
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary listing of active patch groups
|
||||||
|
*/
|
||||||
|
public String getPatchGroupSummary()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Active Patch Groups\n");
|
||||||
|
List<PatchGroupTracker> trackers = new ArrayList<>(mPatchGroupTrackerMap.values());
|
||||||
|
|
||||||
|
if(trackers.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append("None");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(PatchGroupTracker tracker : trackers)
|
||||||
|
{
|
||||||
|
sb.append(" ").append(tracker.mPatchGroupIdentifier).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears any existing patch groups
|
* Clears any existing patch groups
|
||||||
*/
|
*/
|
||||||
public void clear()
|
public void clear()
|
||||||
{
|
{
|
||||||
mPatchGroupMap.clear();
|
mPatchGroupTrackerMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,46 +91,53 @@ public class PatchGroupManager
|
|||||||
* existing patch group is discarded and replaced with the updated version of the patch group.
|
* existing patch group is discarded and replaced with the updated version of the patch group.
|
||||||
*
|
*
|
||||||
* @param patchGroupIdentifier to add or update
|
* @param patchGroupIdentifier to add or update
|
||||||
|
* @return true if the group was added or updated
|
||||||
*/
|
*/
|
||||||
public synchronized void addPatchGroup(PatchGroupIdentifier patchGroupIdentifier)
|
public synchronized boolean addPatchGroup(PatchGroupIdentifier patchGroupIdentifier, long timestamp)
|
||||||
{
|
{
|
||||||
PatchGroup update = patchGroupIdentifier.getValue();
|
PatchGroup update = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
if(update.getPatchGroup().getValue() > 0)
|
int patchGroup = update.getPatchGroup().getValue();
|
||||||
{
|
|
||||||
if(mPatchGroupMap.containsKey(update.getPatchGroup().getValue()))
|
|
||||||
{
|
|
||||||
PatchGroup existingPatchGroup = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue();
|
|
||||||
|
|
||||||
//If the patch group version number is the same, this is an update
|
if(patchGroup > 0)
|
||||||
if(existingPatchGroup.getVersion() == update.getVersion())
|
{
|
||||||
|
if(mPatchGroupTrackerMap.containsKey(patchGroup))
|
||||||
|
{
|
||||||
|
PatchGroupTracker tracker = mPatchGroupTrackerMap.get(patchGroup);
|
||||||
|
|
||||||
|
if(tracker.isStale(timestamp))
|
||||||
{
|
{
|
||||||
existingPatchGroup.addPatchedTalkgroups(update.getPatchedTalkgroupIdentifiers());
|
//Replace the existing patch group if it is stale.
|
||||||
existingPatchGroup.addPatchedRadios(update.getPatchedRadioIdentifiers());
|
mPatchGroupTrackerMap.put(patchGroup, new PatchGroupTracker(patchGroupIdentifier, timestamp));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
//Otherwise, this is a replace operation.
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier);
|
//Update the existing patch group.
|
||||||
|
return tracker.add(patchGroupIdentifier, timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier);
|
mPatchGroupTrackerMap.put(patchGroup, new PatchGroupTracker(patchGroupIdentifier, timestamp));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds any patch group identifiers contained in the list.
|
* Adds any patch group identifiers contained in the list.
|
||||||
|
* @param referenceTimestamp as a reference for checking staleness of existing patch groups.
|
||||||
*/
|
*/
|
||||||
public synchronized void addPatchGroups(List<Identifier> identifiers)
|
public synchronized void addPatchGroups(List<Identifier> identifiers, long referenceTimestamp)
|
||||||
{
|
{
|
||||||
for(Identifier identifier : identifiers)
|
for(Identifier identifier : identifiers)
|
||||||
{
|
{
|
||||||
if(identifier instanceof PatchGroupIdentifier)
|
if(identifier instanceof PatchGroupIdentifier)
|
||||||
{
|
{
|
||||||
addPatchGroup((PatchGroupIdentifier)identifier);
|
addPatchGroup((PatchGroupIdentifier)identifier, referenceTimestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,11 +146,12 @@ public class PatchGroupManager
|
|||||||
* Removes the patch group from this manager if it is currently being managed.
|
* Removes the patch group from this manager if it is currently being managed.
|
||||||
*
|
*
|
||||||
* @param patchGroupIdentifier to remove
|
* @param patchGroupIdentifier to remove
|
||||||
|
* @return true if the patch group was removed.
|
||||||
*/
|
*/
|
||||||
public synchronized void removePatchGroup(PatchGroupIdentifier patchGroupIdentifier)
|
public synchronized boolean removePatchGroup(PatchGroupIdentifier patchGroupIdentifier)
|
||||||
{
|
{
|
||||||
int id = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
int id = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
||||||
mPatchGroupMap.remove(id);
|
return mPatchGroupTrackerMap.remove(id) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,15 +172,16 @@ public class PatchGroupManager
|
|||||||
* Updates the list of identifiers by replacing any talkgroups or patch groups with the current
|
* Updates the list of identifiers by replacing any talkgroups or patch groups with the current
|
||||||
* version of the patch group and a complete listing of the patched talkgroups.
|
* version of the patch group and a complete listing of the patched talkgroups.
|
||||||
* @param identifiers to update
|
* @param identifiers to update
|
||||||
|
* @param referenceTimestamp as a reference for checking staleness of existing patch groups.
|
||||||
* @return list of identifiers
|
* @return list of identifiers
|
||||||
*/
|
*/
|
||||||
public List<Identifier> update(List<Identifier> identifiers)
|
public List<Identifier> update(List<Identifier> identifiers, long referenceTimestamp)
|
||||||
{
|
{
|
||||||
List<Identifier> updated = new ArrayList<>();
|
List<Identifier> updated = new ArrayList<>();
|
||||||
|
|
||||||
for(Identifier identifier: identifiers)
|
for(Identifier identifier: identifiers)
|
||||||
{
|
{
|
||||||
updated.add(update(identifier));
|
updated.add(update(identifier, referenceTimestamp));
|
||||||
}
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
@@ -155,9 +192,10 @@ public class PatchGroupManager
|
|||||||
* if the identifier matches a currently managed patch group.
|
* if the identifier matches a currently managed patch group.
|
||||||
*
|
*
|
||||||
* @param identifier for a talkgroup or a patch group.
|
* @param identifier for a talkgroup or a patch group.
|
||||||
|
* @param referenceTimestamp as a reference for checking staleness of existing patch groups.
|
||||||
* @return current patch group or the original identifier
|
* @return current patch group or the original identifier
|
||||||
*/
|
*/
|
||||||
public Identifier update(Identifier identifier)
|
public Identifier update(Identifier identifier, long referenceTimestamp)
|
||||||
{
|
{
|
||||||
if(identifier != null && identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO)
|
if(identifier != null && identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO)
|
||||||
{
|
{
|
||||||
@@ -166,22 +204,49 @@ public class PatchGroupManager
|
|||||||
case TALKGROUP:
|
case TALKGROUP:
|
||||||
if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||||
{
|
{
|
||||||
Identifier mapValue = mPatchGroupMap.get(talkgroupIdentifier.getValue());
|
int id = talkgroupIdentifier.getValue();
|
||||||
if (mapValue != null)
|
|
||||||
|
PatchGroupTracker tracker = mPatchGroupTrackerMap.get(id);
|
||||||
|
|
||||||
|
if(tracker != null)
|
||||||
{
|
{
|
||||||
return mapValue;
|
if(tracker.isStale(referenceTimestamp))
|
||||||
|
{
|
||||||
|
mPatchGroupTrackerMap.remove(id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Perform substitution - return patch group instead of the original talkgroup
|
||||||
|
return tracker.getPatchGroupIdentifier(referenceTimestamp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PATCH_GROUP:
|
case PATCH_GROUP:
|
||||||
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
{
|
{
|
||||||
int patchGroupId = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
int id = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
||||||
|
|
||||||
Identifier mapValue = mPatchGroupMap.get(patchGroupId);
|
PatchGroupTracker tracker = mPatchGroupTrackerMap.get(id);
|
||||||
if (mapValue != null)
|
|
||||||
|
if(tracker != null)
|
||||||
{
|
{
|
||||||
return mapValue;
|
if(tracker.isStale(referenceTimestamp))
|
||||||
|
{
|
||||||
|
mPatchGroupTrackerMap.put(id, new PatchGroupTracker(patchGroupIdentifier, referenceTimestamp));
|
||||||
|
mPatchGroupTrackerMap.remove(id);
|
||||||
|
}
|
||||||
|
else if(tracker.getPatchGroupIdentifier(referenceTimestamp).getValue().getVersion() !=
|
||||||
|
patchGroupIdentifier.getValue().getVersion())
|
||||||
|
{
|
||||||
|
mPatchGroupTrackerMap.put(id, new PatchGroupTracker(patchGroupIdentifier, referenceTimestamp));
|
||||||
|
mPatchGroupTrackerMap.remove(id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Perform substitution - return patch group instead of the original talkgroup
|
||||||
|
return tracker.getPatchGroupIdentifier(referenceTimestamp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -190,4 +255,107 @@ public class PatchGroupManager
|
|||||||
|
|
||||||
return identifier;
|
return identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the freshness of a patch group and any member talkgroup and radio identifiers to ensure that stale
|
||||||
|
* patch group identifiers are removed.
|
||||||
|
*/
|
||||||
|
public class PatchGroupTracker
|
||||||
|
{
|
||||||
|
private PatchGroupIdentifier mPatchGroupIdentifier;
|
||||||
|
private long mLastUpdateTimestamp;
|
||||||
|
private Map<TalkgroupIdentifier,Long> mTalkgroupTimestampMap = new HashMap<>();
|
||||||
|
private Map<RadioIdentifier,Long> mRadioTimestampMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param patchGroupIdentifier
|
||||||
|
*/
|
||||||
|
public PatchGroupTracker(PatchGroupIdentifier patchGroupIdentifier, long timestamp)
|
||||||
|
{
|
||||||
|
mPatchGroupIdentifier = patchGroupIdentifier;
|
||||||
|
add(patchGroupIdentifier, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch group monitored by this tracker. This method will cleanup and remove any stale radio or talkgroup
|
||||||
|
* identifiers on access.
|
||||||
|
*
|
||||||
|
* Note: this tracker should be checked for freshness by first checking the isStale() method before accessing
|
||||||
|
* the tracked patch group by this method.
|
||||||
|
*
|
||||||
|
* @param referenceTimestamp for comparing stored radio and talkgroup values for staleness.
|
||||||
|
* @return current version of the talkgroup
|
||||||
|
*/
|
||||||
|
public PatchGroupIdentifier getPatchGroupIdentifier(long referenceTimestamp)
|
||||||
|
{
|
||||||
|
//Remove stale talkgroups
|
||||||
|
List<Map.Entry<TalkgroupIdentifier,Long>> toRemove = mTalkgroupTimestampMap.entrySet().stream()
|
||||||
|
.filter(entry -> isStale(entry.getValue(), referenceTimestamp)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
for(Map.Entry<TalkgroupIdentifier,Long> entry: toRemove)
|
||||||
|
{
|
||||||
|
mTalkgroupTimestampMap.remove(entry.getKey());
|
||||||
|
mPatchGroupIdentifier.getValue().removePatchedTalkgroup(entry.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove stale radios
|
||||||
|
List<Map.Entry<RadioIdentifier,Long>> toRemove2 = mRadioTimestampMap.entrySet().stream()
|
||||||
|
.filter(entry -> isStale(entry.getValue(), referenceTimestamp)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
for(Map.Entry<RadioIdentifier,Long> entry: toRemove2)
|
||||||
|
{
|
||||||
|
mRadioTimestampMap.remove(entry.getKey());
|
||||||
|
mPatchGroupIdentifier.getValue().removePatchedRadio(entry.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPatchGroupIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the stored timestamp is stale relative to the reference timestamp.
|
||||||
|
* @param storedTimestamp to compare
|
||||||
|
* @param referenceTimestamp representing the current time.
|
||||||
|
* @return true of the stored timestamp is stale.
|
||||||
|
*/
|
||||||
|
private boolean isStale(long storedTimestamp, long referenceTimestamp)
|
||||||
|
{
|
||||||
|
return referenceTimestamp - storedTimestamp > PATCH_GROUP_FRESHNESS_THRESHOLD_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the tracked patch group is stale and hasn't received any add/updates within a period of time.
|
||||||
|
* @param referenceTimestamp representing now for comparison against the patch groups update timestamp.
|
||||||
|
* @return true if the tracked patch group is stale.
|
||||||
|
*/
|
||||||
|
public boolean isStale(long referenceTimestamp)
|
||||||
|
{
|
||||||
|
return isStale(mLastUpdateTimestamp, referenceTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or update the patch group and update the freshness timestamp.
|
||||||
|
* @param patchGroupIdentifier containing an add value.
|
||||||
|
*/
|
||||||
|
public boolean add(PatchGroupIdentifier patchGroupIdentifier, long timestamp)
|
||||||
|
{
|
||||||
|
mLastUpdateTimestamp = timestamp;
|
||||||
|
|
||||||
|
boolean added = false;
|
||||||
|
|
||||||
|
for(TalkgroupIdentifier talkgroup: patchGroupIdentifier.getValue().getPatchedTalkgroupIdentifiers())
|
||||||
|
{
|
||||||
|
mTalkgroupTimestampMap.put(talkgroup, timestamp);
|
||||||
|
added |= mPatchGroupIdentifier.getValue().addPatchedTalkgroup(talkgroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(RadioIdentifier radio: patchGroupIdentifier.getValue().getPatchedRadioIdentifiers())
|
||||||
|
{
|
||||||
|
mRadioTimestampMap.put(radio, timestamp);
|
||||||
|
added |= mPatchGroupIdentifier.getValue().addPatchedRadio(radio);
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -27,13 +27,25 @@ import io.github.dsheirer.identifier.IdentifierCollection;
|
|||||||
*/
|
*/
|
||||||
public class PatchGroupPreLoadDataContent extends PreloadDataContent<IdentifierCollection>
|
public class PatchGroupPreLoadDataContent extends PreloadDataContent<IdentifierCollection>
|
||||||
{
|
{
|
||||||
|
private long mTimestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
*
|
*
|
||||||
* @param data to preload
|
* @param data to preload
|
||||||
*/
|
*/
|
||||||
public PatchGroupPreLoadDataContent(IdentifierCollection data)
|
public PatchGroupPreLoadDataContent(IdentifierCollection data, long timestamp)
|
||||||
{
|
{
|
||||||
super(data);
|
super(data);
|
||||||
|
mTimestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Freshness timestamp for the preload data.
|
||||||
|
* @return timestamp milliseconds.
|
||||||
|
*/
|
||||||
|
public long getTimestamp()
|
||||||
|
{
|
||||||
|
return mTimestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.identifier.radio;
|
package io.github.dsheirer.identifier.radio;
|
||||||
@@ -25,29 +22,74 @@ package io.github.dsheirer.identifier.radio;
|
|||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fully qualified radio identifier
|
* Fully qualified radio identifier. This is used for roaming radios or any time there is a need to fully qualify a
|
||||||
|
* radio.
|
||||||
*/
|
*/
|
||||||
public abstract class FullyQualifiedRadioIdentifier extends RadioIdentifier
|
public abstract class FullyQualifiedRadioIdentifier extends RadioIdentifier
|
||||||
{
|
{
|
||||||
private int mWacn;
|
private int mWacn;
|
||||||
private int mSystem;
|
private int mSystem;
|
||||||
|
private int mRadio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
* @param wacn
|
* @param localAddress radio identifier, aka alias. This can be the same as the radio ID when the fully qualified radio
|
||||||
* @param system
|
* is not being aliased on a local radio system.
|
||||||
* @param id
|
* @param wacn of the home network for the radio.
|
||||||
|
* @param system of the home network for the radio.
|
||||||
|
* @param id of the radio within the home network.
|
||||||
*/
|
*/
|
||||||
public FullyQualifiedRadioIdentifier(int wacn, int system, int id, Role role)
|
public FullyQualifiedRadioIdentifier(int localAddress, int wacn, int system, int id, Role role)
|
||||||
{
|
{
|
||||||
super(id, role);
|
super(localAddress > 0 ? localAddress : id, role);
|
||||||
mWacn = wacn;
|
mWacn = wacn;
|
||||||
mSystem = system;
|
mSystem = system;
|
||||||
|
mRadio = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWacn()
|
||||||
|
{
|
||||||
|
return mWacn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSystem()
|
||||||
|
{
|
||||||
|
return mSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRadio()
|
||||||
|
{
|
||||||
|
return mRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully qualified radio identity.
|
||||||
|
* @return radio identity
|
||||||
|
*/
|
||||||
|
public String getFullyQualifiedRadioAddress()
|
||||||
|
{
|
||||||
|
return mWacn + "." + mSystem + "." + mRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the radio identify is aliased with a persona value that is different from the radio ID.
|
||||||
|
* @return true if aliased.
|
||||||
|
*/
|
||||||
|
public boolean isAliased()
|
||||||
|
{
|
||||||
|
return getValue() != mRadio;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return mWacn + "." + mSystem + "." + super.toString();
|
if(isAliased())
|
||||||
|
{
|
||||||
|
return super.toString() + "(" + getFullyQualifiedRadioAddress() + ")";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getFullyQualifiedRadioAddress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,14 @@ 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.integer.IntegerIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radio identifier.
|
||||||
|
*
|
||||||
|
* Note: this class overrides the .equals method to ensure that all radios and subclasses can be compared using
|
||||||
|
* the radio value, regardless if it is a simple radio or a fully qualified radio identifier.
|
||||||
|
*/
|
||||||
public abstract class RadioIdentifier extends IntegerIdentifier
|
public abstract class RadioIdentifier extends IntegerIdentifier
|
||||||
{
|
{
|
||||||
public RadioIdentifier(Integer value, Role role)
|
public RadioIdentifier(Integer value, Role role)
|
||||||
@@ -39,4 +46,28 @@ public abstract class RadioIdentifier extends IntegerIdentifier
|
|||||||
{
|
{
|
||||||
return getValue() > 0;
|
return getValue() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides to compare just the radio value, class, form, role and protocol. This allows a fully qualified
|
||||||
|
* radio identifier to be equivalent to a standard radio identifier for the traffic channel manager.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if(this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o instanceof RadioIdentifier radio)
|
||||||
|
{
|
||||||
|
return (getValue().intValue() == radio.getValue().intValue()) &&
|
||||||
|
getIdentifierClass() == radio.getIdentifierClass() &&
|
||||||
|
getForm() == radio.getForm() &&
|
||||||
|
getRole() == radio.getRole() &&
|
||||||
|
getProtocol() == radio.getProtocol();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.identifier.talkgroup;
|
package io.github.dsheirer.identifier.talkgroup;
|
||||||
@@ -31,23 +28,67 @@ public abstract class FullyQualifiedTalkgroupIdentifier extends TalkgroupIdentif
|
|||||||
{
|
{
|
||||||
private int mWacn;
|
private int mWacn;
|
||||||
private int mSystem;
|
private int mSystem;
|
||||||
|
private int mTalkgroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
* @param wacn
|
* @param localAddress used on the local system as an alias to the fully qualified talkgroup.
|
||||||
* @param system
|
* @param wacn for the talkgroup home system.
|
||||||
* @param id
|
* @param system for the talkgroup home system.
|
||||||
|
* @param id for the talkgroup within the home system.
|
||||||
|
* @param role played by the talkgroup.
|
||||||
*/
|
*/
|
||||||
public FullyQualifiedTalkgroupIdentifier(int wacn, int system, int id, Role role)
|
public FullyQualifiedTalkgroupIdentifier(int localAddress, int wacn, int system, int id, Role role)
|
||||||
{
|
{
|
||||||
super(id, role);
|
super(localAddress > 0 ? localAddress : id, role);
|
||||||
mWacn = wacn;
|
mWacn = wacn;
|
||||||
mSystem = system;
|
mSystem = system;
|
||||||
|
mTalkgroup = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWacn()
|
||||||
|
{
|
||||||
|
return mWacn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSystem()
|
||||||
|
{
|
||||||
|
return mSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTalkgroup()
|
||||||
|
{
|
||||||
|
return mTalkgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully qualified talkgroup identity.
|
||||||
|
* @return talkgroup identity
|
||||||
|
*/
|
||||||
|
public String getFullyQualifiedTalkgroupAddress()
|
||||||
|
{
|
||||||
|
return mWacn + "." + mSystem + "." + mTalkgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the talkgroup identify is aliased with a persona value that is different from the talkgroup ID.
|
||||||
|
* @return true if aliased.
|
||||||
|
*/
|
||||||
|
public boolean isAliased()
|
||||||
|
{
|
||||||
|
return getValue() != mTalkgroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return mWacn + "." + mSystem + "." + super.toString();
|
if(isAliased())
|
||||||
|
{
|
||||||
|
return super.toString() + "(" + getFullyQualifiedTalkgroupAddress() + ")";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getFullyQualifiedTalkgroupAddress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,19 @@ 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.integer.IntegerIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup identifier.
|
||||||
|
*
|
||||||
|
* Note: this class overrides the .equals method to ensure that all talkgroups and subclasses can be compared using
|
||||||
|
* the talkgroup value, regardless if it is a simple talkgroup or a fully qualified talkgroup identifier.
|
||||||
|
*/
|
||||||
public abstract class TalkgroupIdentifier extends IntegerIdentifier
|
public abstract class TalkgroupIdentifier extends IntegerIdentifier
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value for the talkgroup
|
||||||
|
* @param role for the talkgroup
|
||||||
|
*/
|
||||||
public TalkgroupIdentifier(Integer value, Role role)
|
public TalkgroupIdentifier(Integer value, Role role)
|
||||||
{
|
{
|
||||||
super(value, IdentifierClass.USER, Form.TALKGROUP, role);
|
super(value, IdentifierClass.USER, Form.TALKGROUP, role);
|
||||||
@@ -39,4 +50,28 @@ public abstract class TalkgroupIdentifier extends IntegerIdentifier
|
|||||||
{
|
{
|
||||||
return getValue() > 0;
|
return getValue() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides to compare just the talkgroup value, class, form, role and protocol. This allows a fully qualified
|
||||||
|
* talkgroup identifier to be equivalent to a standard talkgroup identifier for the traffic channel manager.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if(this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(o instanceof TalkgroupIdentifier tg)
|
||||||
|
{
|
||||||
|
return (getValue().intValue() == tg.getValue().intValue()) &&
|
||||||
|
getIdentifierClass() == tg.getIdentifierClass() &&
|
||||||
|
getForm() == tg.getForm() &&
|
||||||
|
getRole() == tg.getRole() &&
|
||||||
|
getProtocol() == tg.getProtocol();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
191
src/main/java/io/github/dsheirer/message/AbstractMessage.java
Normal file
191
src/main/java/io/github/dsheirer/message/AbstractMessage.java
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.message;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.FragmentedIntField;
|
||||||
|
import io.github.dsheirer.bits.IntField;
|
||||||
|
import io.github.dsheirer.bits.LongField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base message class with general utility methods for accessing fields in BinaryMessages.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractMessage
|
||||||
|
{
|
||||||
|
private CorrectedBinaryMessage mMessage;
|
||||||
|
private int mOffset = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param message containing the message bits.
|
||||||
|
* @param offset to the start of the message within the corrected binary message.
|
||||||
|
*/
|
||||||
|
public AbstractMessage(CorrectedBinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
mMessage = message;
|
||||||
|
mOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param message bits
|
||||||
|
*/
|
||||||
|
public AbstractMessage(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
this(message, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message is offset within the corrected binary message.
|
||||||
|
* @return true of offset is non-zero.
|
||||||
|
*/
|
||||||
|
private boolean hasOffset()
|
||||||
|
{
|
||||||
|
return mOffset > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset to the start of the message (fragement) within the corrected binary message.
|
||||||
|
*/
|
||||||
|
public int getOffset()
|
||||||
|
{
|
||||||
|
return mOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary (bits) message for this message.
|
||||||
|
* @return corrected binary message
|
||||||
|
*/
|
||||||
|
public CorrectedBinaryMessage getMessage()
|
||||||
|
{
|
||||||
|
return mMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the field has a non-zero value, meaning that any of the bits for the field are set to a one.
|
||||||
|
* @param field to inspect.
|
||||||
|
* @return true if the field has a non-zero value.
|
||||||
|
*/
|
||||||
|
public boolean hasInt(IntField field)
|
||||||
|
{
|
||||||
|
if(hasOffset())
|
||||||
|
{
|
||||||
|
return getMessage().hasInt(field, getOffset());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getMessage().hasInt(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the integer field value and formats it as hexadecimal number
|
||||||
|
* @param field to parse
|
||||||
|
* @param places to format (ie 2 for a byte, 4 for 2-bytes, etc.)
|
||||||
|
* @return formatted hex value.
|
||||||
|
*/
|
||||||
|
public String getIntAsHex(IntField field, int places)
|
||||||
|
{
|
||||||
|
int value = getInt(field);
|
||||||
|
String hex = Integer.toHexString(value).toUpperCase();
|
||||||
|
while(hex.length() < places)
|
||||||
|
{
|
||||||
|
hex = "0" + hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the integer value for the specified field.
|
||||||
|
* @param field to access
|
||||||
|
* @return integer value for the field.
|
||||||
|
*/
|
||||||
|
public int getInt(IntField field)
|
||||||
|
{
|
||||||
|
if(hasOffset())
|
||||||
|
{
|
||||||
|
return getMessage().getInt(field, getOffset());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getMessage().getInt(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the field has a non-zero value, meaning that any of the bits for the field are set to a one.
|
||||||
|
* @param field to inspect.
|
||||||
|
* @return true if the field has a non-zero value.
|
||||||
|
*/
|
||||||
|
public boolean hasInt(FragmentedIntField field)
|
||||||
|
{
|
||||||
|
if(hasOffset())
|
||||||
|
{
|
||||||
|
return getMessage().hasInt(field, getOffset());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getMessage().hasInt(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the integer value for the specified field.
|
||||||
|
* @param field to access
|
||||||
|
* @return integer value for the field.
|
||||||
|
*/
|
||||||
|
public int getInt(FragmentedIntField field)
|
||||||
|
{
|
||||||
|
if(hasOffset())
|
||||||
|
{
|
||||||
|
return getMessage().getInt(field, getOffset());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getMessage().getInt(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the long value for the specified field.
|
||||||
|
* @param field to access
|
||||||
|
* @return value for the field.
|
||||||
|
*/
|
||||||
|
public long getLong(LongField field)
|
||||||
|
{
|
||||||
|
if(hasOffset())
|
||||||
|
{
|
||||||
|
return getMessage().getLong(field, getOffset());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return getMessage().getLong(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to format an octet as hex.
|
||||||
|
*/
|
||||||
|
public static String formatOctetAsHex(int value)
|
||||||
|
{
|
||||||
|
return String.format("%02X", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/main/java/io/github/dsheirer/message/TimeslotMessage.java
Normal file
103
src/main/java/io/github/dsheirer/message/TimeslotMessage.java
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.message;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeslot message base class.
|
||||||
|
*/
|
||||||
|
public abstract class TimeslotMessage extends AbstractMessage
|
||||||
|
{
|
||||||
|
public static final int TIMESLOT_0 = 0;
|
||||||
|
public static final int TIMESLOT_1 = 1;
|
||||||
|
public static final int TIMESLOT_2 = 2;
|
||||||
|
private long mTimestamp;
|
||||||
|
private int mTimeslot;
|
||||||
|
private boolean mValid = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param message containing binary fields that may contain fields outside of the scope of this class and thus
|
||||||
|
* requires an offset value that points to the start of this message structure within the larger message context.
|
||||||
|
* @param offset in the binary message to the start of this chunk of the message.
|
||||||
|
* @param timeslot for this message
|
||||||
|
* @param timestamp for this message
|
||||||
|
*/
|
||||||
|
public TimeslotMessage(CorrectedBinaryMessage message, int offset, int timeslot, long timestamp)
|
||||||
|
{
|
||||||
|
super(message, offset);
|
||||||
|
mTimeslot = timeslot;
|
||||||
|
mTimestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param message containing binary fields.
|
||||||
|
* @param timeslot for this message
|
||||||
|
* @param timestamp for this message
|
||||||
|
*/
|
||||||
|
public TimeslotMessage(CorrectedBinaryMessage message, int timeslot, long timestamp)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
mTimeslot = timeslot;
|
||||||
|
mTimestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeslot for this message.
|
||||||
|
* @return timeslot, default of 0 for no timeslot.
|
||||||
|
*/
|
||||||
|
public int getTimeslot()
|
||||||
|
{
|
||||||
|
return mTimeslot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp when the message was received or processed. This timestamp should be as close to
|
||||||
|
* accurate as possible.
|
||||||
|
*/
|
||||||
|
public long getTimestamp()
|
||||||
|
{
|
||||||
|
return mTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoded textual representation of the message
|
||||||
|
*/
|
||||||
|
public abstract String toString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the message is valid and has passed crc/integrity checks
|
||||||
|
*/
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return mValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the validity flag to indicate if the message is valid and passed any CRC or integrity checks.
|
||||||
|
* @param valid flag
|
||||||
|
*/
|
||||||
|
public void setValid(boolean valid)
|
||||||
|
{
|
||||||
|
mValid = valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ import io.github.dsheirer.controller.channel.map.ChannelMapModel;
|
|||||||
import io.github.dsheirer.filter.AllPassFilter;
|
import io.github.dsheirer.filter.AllPassFilter;
|
||||||
import io.github.dsheirer.filter.FilterSet;
|
import io.github.dsheirer.filter.FilterSet;
|
||||||
import io.github.dsheirer.filter.IFilter;
|
import io.github.dsheirer.filter.IFilter;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroupManager;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.message.MessageDirection;
|
import io.github.dsheirer.message.MessageDirection;
|
||||||
import io.github.dsheirer.module.Module;
|
import io.github.dsheirer.module.Module;
|
||||||
@@ -51,6 +52,7 @@ import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
|
|||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
|
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet;
|
import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
|
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
|
||||||
@@ -90,6 +92,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25P1MessageFi
|
|||||||
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK;
|
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
|
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.filter.P25P2MessageFilterSet;
|
import io.github.dsheirer.module.decode.p25.phase2.message.filter.P25P2MessageFilterSet;
|
||||||
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
|
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
|
||||||
import io.github.dsheirer.module.decode.passport.PassportDecoder;
|
import io.github.dsheirer.module.decode.passport.PassportDecoder;
|
||||||
@@ -149,7 +152,7 @@ public class DecoderFactory
|
|||||||
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager,
|
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager,
|
||||||
IChannelDescriptor channelDescriptor)
|
IChannelDescriptor channelDescriptor)
|
||||||
{
|
{
|
||||||
List<Module> modules = new ArrayList<Module>();
|
List<Module> modules = new ArrayList<>();
|
||||||
|
|
||||||
AliasList aliasList = aliasModel.getAliasList(channel.getAliasListName());
|
AliasList aliasList = aliasModel.getAliasList(channel.getAliasListName());
|
||||||
modules.add(new AliasActionManager(aliasList));
|
modules.add(new AliasActionManager(aliasList));
|
||||||
@@ -178,7 +181,8 @@ public class DecoderFactory
|
|||||||
processLTRNet(channel, modules, aliasList, (DecodeConfigLTRNet) decodeConfig);
|
processLTRNet(channel, modules, aliasList, (DecodeConfigLTRNet) decodeConfig);
|
||||||
break;
|
break;
|
||||||
case MPT1327:
|
case MPT1327:
|
||||||
processMPT1327(channelMapModel, channel, modules, aliasList, channelType, (DecodeConfigMPT1327) decodeConfig);
|
processMPT1327(channelMapModel, channel, modules, aliasList, channelType,
|
||||||
|
(DecodeConfigMPT1327) decodeConfig, userPreferences);
|
||||||
break;
|
break;
|
||||||
case PASSPORT:
|
case PASSPORT:
|
||||||
processPassport(channel, modules, aliasList, decodeConfig);
|
processPassport(channel, modules, aliasList, decodeConfig);
|
||||||
@@ -187,7 +191,7 @@ public class DecoderFactory
|
|||||||
processP25Phase1(channel, userPreferences, modules, aliasList, channelType, (DecodeConfigP25Phase1) decodeConfig);
|
processP25Phase1(channel, userPreferences, modules, aliasList, channelType, (DecodeConfigP25Phase1) decodeConfig);
|
||||||
break;
|
break;
|
||||||
case P25_PHASE2:
|
case P25_PHASE2:
|
||||||
processP25Phase2(channel, userPreferences, modules, aliasList);
|
processP25Phase2(channel, userPreferences, modules, aliasList, trafficChannelManager);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]");
|
throw new IllegalArgumentException("Unknown decoder type [" + decodeConfig.getDecoderType().toString() + "]");
|
||||||
@@ -202,14 +206,53 @@ public class DecoderFactory
|
|||||||
* @param userPreferences reference
|
* @param userPreferences reference
|
||||||
* @param modules collection to add to
|
* @param modules collection to add to
|
||||||
* @param aliasList for the channel
|
* @param aliasList for the channel
|
||||||
|
* @param trafficChannelManager from parent, if this is for a traffic channel, otherwise this can be null and it
|
||||||
|
* will be created automatically.
|
||||||
*/
|
*/
|
||||||
private static void processP25Phase2(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList) {
|
private static void processP25Phase2(Channel channel, UserPreferences userPreferences, List<Module> modules,
|
||||||
|
AliasList aliasList, TrafficChannelManager trafficChannelManager)
|
||||||
|
{
|
||||||
|
|
||||||
modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration()));
|
modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration()));
|
||||||
|
|
||||||
modules.add(new P25P2DecoderState(channel, 0));
|
P25TrafficChannelManager p25TrafficChannelManager = null;
|
||||||
modules.add(new P25P2DecoderState(channel, 1));
|
|
||||||
modules.add(new P25P2AudioModule(userPreferences, 0, aliasList));
|
if(channel.getChannelType() == ChannelType.STANDARD)
|
||||||
modules.add(new P25P2AudioModule(userPreferences, 1, aliasList));
|
{
|
||||||
|
p25TrafficChannelManager = new P25TrafficChannelManager(channel);
|
||||||
|
}
|
||||||
|
else if(trafficChannelManager instanceof P25TrafficChannelManager p25)
|
||||||
|
{
|
||||||
|
p25TrafficChannelManager = p25;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p25TrafficChannelManager = new P25TrafficChannelManager(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Only add traffic channel manager to the modules if this is the control channel
|
||||||
|
if(channel.isStandardChannel())
|
||||||
|
{
|
||||||
|
modules.add(p25TrafficChannelManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchGroupManager patchGroupManager = new PatchGroupManager();
|
||||||
|
modules.add(new P25P2DecoderState(channel, P25P2Message.TIMESLOT_1, p25TrafficChannelManager, patchGroupManager));
|
||||||
|
modules.add(new P25P2DecoderState(channel, P25P2Message.TIMESLOT_2, p25TrafficChannelManager, patchGroupManager));
|
||||||
|
modules.add(new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_1, aliasList));
|
||||||
|
modules.add(new P25P2AudioModule(userPreferences, P25P2Message.TIMESLOT_2, aliasList));
|
||||||
|
|
||||||
|
|
||||||
|
//Add a channel rotation monitor when we have multiple control channel frequencies specified
|
||||||
|
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency &&
|
||||||
|
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies())
|
||||||
|
{
|
||||||
|
List<State> activeStates = new ArrayList<>();
|
||||||
|
activeStates.add(State.CONTROL);
|
||||||
|
modules.add(new ChannelRotationMonitor(activeStates,
|
||||||
|
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay(),
|
||||||
|
userPreferences));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,7 +262,8 @@ public class DecoderFactory
|
|||||||
* @param modules collection to add to
|
* @param modules collection to add to
|
||||||
* @param aliasList for the channel
|
* @param aliasList for the channel
|
||||||
*/
|
*/
|
||||||
private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigP25Phase1 decodeConfig) {
|
private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigP25Phase1 decodeConfig)
|
||||||
|
{
|
||||||
DecodeConfigP25Phase1 p25Config = decodeConfig;
|
DecodeConfigP25Phase1 p25Config = decodeConfig;
|
||||||
|
|
||||||
switch(p25Config.getModulation())
|
switch(p25Config.getModulation())
|
||||||
@@ -249,13 +293,12 @@ public class DecoderFactory
|
|||||||
modules.add(new P25P1AudioModule(userPreferences, aliasList));
|
modules.add(new P25P1AudioModule(userPreferences, aliasList));
|
||||||
|
|
||||||
//Add a channel rotation monitor when we have multiple control channel frequencies specified
|
//Add a channel rotation monitor when we have multiple control channel frequencies specified
|
||||||
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency &&
|
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf &&
|
||||||
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies())
|
sctmf.hasMultipleFrequencies())
|
||||||
{
|
{
|
||||||
List<State> activeStates = new ArrayList<>();
|
List<State> activeStates = new ArrayList<>();
|
||||||
activeStates.add(State.CONTROL);
|
activeStates.add(State.CONTROL);
|
||||||
modules.add(new ChannelRotationMonitor(activeStates,
|
modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences));
|
||||||
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +328,10 @@ public class DecoderFactory
|
|||||||
* @param channelType for control or traffic
|
* @param channelType for control or traffic
|
||||||
* @param decodeConfig configuration
|
* @param decodeConfig configuration
|
||||||
*/
|
*/
|
||||||
private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig) {
|
private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, List<Module> modules,
|
||||||
|
AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig,
|
||||||
|
UserPreferences userPreferences)
|
||||||
|
{
|
||||||
DecodeConfigMPT1327 mptConfig = decodeConfig;
|
DecodeConfigMPT1327 mptConfig = decodeConfig;
|
||||||
ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName());
|
ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName());
|
||||||
Sync sync = mptConfig.getSync();
|
Sync sync = mptConfig.getSync();
|
||||||
@@ -317,13 +363,12 @@ public class DecoderFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Add a channel rotation monitor when we have multiple control channel frequencies specified
|
//Add a channel rotation monitor when we have multiple control channel frequencies specified
|
||||||
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency &&
|
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf &&
|
||||||
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies())
|
sctmf.hasMultipleFrequencies())
|
||||||
{
|
{
|
||||||
List<State> activeStates = new ArrayList<>();
|
List<State> activeStates = new ArrayList<>();
|
||||||
activeStates.add(State.CONTROL);
|
activeStates.add(State.CONTROL);
|
||||||
modules.add(new ChannelRotationMonitor(activeStates,
|
modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences));
|
||||||
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,8 +485,8 @@ public class DecoderFactory
|
|||||||
modules.add(dmrTrafficChannelManager);
|
modules.add(dmrTrafficChannelManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
DMRDecoderState state1 = new DMRDecoderState(channel, 1, dmrTrafficChannelManager);
|
DMRDecoderState state1 = new DMRDecoderState(channel, DMRMessage.TIMESLOT_1, dmrTrafficChannelManager);
|
||||||
DMRDecoderState state2 = new DMRDecoderState(channel, 2, dmrTrafficChannelManager);
|
DMRDecoderState state2 = new DMRDecoderState(channel, DMRMessage.TIMESLOT_2, dmrTrafficChannelManager);
|
||||||
|
|
||||||
//Register the states with each other so that they can pass Cap+ site status messaging to resolve current channel
|
//Register the states with each other so that they can pass Cap+ site status messaging to resolve current channel
|
||||||
state1.setSisterDecoderState(state2);
|
state1.setSisterDecoderState(state2);
|
||||||
@@ -457,7 +502,7 @@ public class DecoderFactory
|
|||||||
lsn.setTimeslotFrequency(rest.getTimeslotFrequency());
|
lsn.setTimeslotFrequency(rest.getTimeslotFrequency());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(lsn.getTimeslot() == 1)
|
if(lsn.getTimeslot() == DMRMessage.TIMESLOT_1)
|
||||||
{
|
{
|
||||||
state1.setCurrentChannel(lsn);
|
state1.setCurrentChannel(lsn);
|
||||||
state2.setCurrentChannel(lsn.getSisterTimeslot());
|
state2.setCurrentChannel(lsn.getSisterTimeslot());
|
||||||
@@ -473,7 +518,7 @@ public class DecoderFactory
|
|||||||
{
|
{
|
||||||
DecodeEvent event = decodeConfig.getChannelGrantEvent();
|
DecodeEvent event = decodeConfig.getChannelGrantEvent();
|
||||||
|
|
||||||
if(decodeConfig.getChannelGrantEvent().getTimeslot() == 1)
|
if(decodeConfig.getChannelGrantEvent().getTimeslot() == DMRMessage.TIMESLOT_1)
|
||||||
{
|
{
|
||||||
state1.setCurrentCallEvent(event);
|
state1.setCurrentCallEvent(event);
|
||||||
|
|
||||||
@@ -495,17 +540,16 @@ public class DecoderFactory
|
|||||||
|
|
||||||
modules.add(state1);
|
modules.add(state1);
|
||||||
modules.add(state2);
|
modules.add(state2);
|
||||||
modules.add(new DMRAudioModule(userPreferences, aliasList, 1));
|
modules.add(new DMRAudioModule(userPreferences, aliasList, DMRMessage.TIMESLOT_1));
|
||||||
modules.add(new DMRAudioModule(userPreferences, aliasList, 2));
|
modules.add(new DMRAudioModule(userPreferences, aliasList, DMRMessage.TIMESLOT_2));
|
||||||
|
|
||||||
//Add a channel rotation monitor when we have multiple control channel frequencies specified
|
//Add a channel rotation monitor when we have multiple control channel frequencies specified
|
||||||
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency &&
|
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency sctmf &&
|
||||||
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies())
|
sctmf.hasMultipleFrequencies())
|
||||||
{
|
{
|
||||||
List<State> activeStates = new ArrayList<>();
|
List<State> activeStates = new ArrayList<>();
|
||||||
activeStates.add(State.CONTROL);
|
activeStates.add(State.CONTROL);
|
||||||
modules.add(new ChannelRotationMonitor(activeStates,
|
modules.add(new ChannelRotationMonitor(activeStates, sctmf.getFrequencyRotationDelay(), userPreferences));
|
||||||
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.config;
|
package io.github.dsheirer.module.decode.config;
|
||||||
|
|
||||||
@@ -34,8 +31,7 @@ import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet;
|
|||||||
import io.github.dsheirer.module.decode.ltrstandard.DecodeConfigLTRStandard;
|
import io.github.dsheirer.module.decode.ltrstandard.DecodeConfigLTRStandard;
|
||||||
import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
|
import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
|
||||||
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
|
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
|
||||||
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
|
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
|
||||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
|
|
||||||
@@ -47,10 +43,9 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
|||||||
@JsonSubTypes.Type(value = DecodeConfigLTRStandard.class, name = "decodeConfigLTRStandard"),
|
@JsonSubTypes.Type(value = DecodeConfigLTRStandard.class, name = "decodeConfigLTRStandard"),
|
||||||
@JsonSubTypes.Type(value = DecodeConfigMPT1327.class, name = "decodeConfigMPT1327"),
|
@JsonSubTypes.Type(value = DecodeConfigMPT1327.class, name = "decodeConfigMPT1327"),
|
||||||
@JsonSubTypes.Type(value = DecodeConfigNBFM.class, name = "decodeConfigNBFM"),
|
@JsonSubTypes.Type(value = DecodeConfigNBFM.class, name = "decodeConfigNBFM"),
|
||||||
@JsonSubTypes.Type(value = DecodeConfigP25Phase1.class, name = "decodeConfigP25Phase1"),
|
@JsonSubTypes.Type(value = DecodeConfigP25.class, name = "decodeConfigP25"),
|
||||||
@JsonSubTypes.Type(value = DecodeConfigP25Phase2.class, name = "decodeConfigP25Phase2"),
|
|
||||||
@JsonSubTypes.Type(value = DecodeConfigPassport.class, name = "decodeConfigPassport"),
|
@JsonSubTypes.Type(value = DecodeConfigPassport.class, name = "decodeConfigPassport"),
|
||||||
@JsonSubTypes.Type(value = DecodeConfigDMR.class, name = "decodeConfigDMR")
|
@JsonSubTypes.Type(value = DecodeConfigDMR.class, name = "decodeConfigDMR")
|
||||||
})
|
})
|
||||||
@JacksonXmlRootElement(localName = "decode_configuration")
|
@JacksonXmlRootElement(localName = "decode_configuration")
|
||||||
public abstract class DecodeConfiguration extends Configuration
|
public abstract class DecodeConfiguration extends Configuration
|
||||||
@@ -58,10 +53,7 @@ public abstract class DecodeConfiguration extends Configuration
|
|||||||
public static final int DEFAULT_CALL_TIMEOUT_DELAY_SECONDS = 45;
|
public static final int DEFAULT_CALL_TIMEOUT_DELAY_SECONDS = 45;
|
||||||
public static final int CALL_TIMEOUT_MINIMUM = 1;
|
public static final int CALL_TIMEOUT_MINIMUM = 1;
|
||||||
public static final int CALL_TIMEOUT_MAXIMUM = 180;
|
public static final int CALL_TIMEOUT_MAXIMUM = 180;
|
||||||
|
public static final int TRAFFIC_CHANNEL_LIMIT_DEFAULT = 20;
|
||||||
public static final int TRAFFIC_CHANNEL_LIMIT_DEFAULT = 3;
|
|
||||||
public static final int TRAFFIC_CHANNEL_LIMIT_MINIMUM = 0;
|
|
||||||
public static final int TRAFFIC_CHANNEL_LIMIT_MAXIMUM = 30;
|
|
||||||
|
|
||||||
public DecodeConfiguration()
|
public DecodeConfiguration()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
private DMRNetworkConfigurationMonitor mNetworkConfigurationMonitor;
|
private DMRNetworkConfigurationMonitor mNetworkConfigurationMonitor;
|
||||||
private DMRTrafficChannelManager mTrafficChannelManager;
|
private DMRTrafficChannelManager mTrafficChannelManager;
|
||||||
private DecodeEvent mCurrentCallEvent;
|
private DecodeEvent mCurrentCallEvent;
|
||||||
private long mCurrentFrequency;
|
|
||||||
private boolean mIgnoreCRCChecksums;
|
private boolean mIgnoreCRCChecksums;
|
||||||
private DMRDecoderState mSisterDecoderState;
|
private DMRDecoderState mSisterDecoderState;
|
||||||
|
|
||||||
@@ -275,8 +274,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
super.reset();
|
super.reset();
|
||||||
resetState();
|
resetState();
|
||||||
|
setCurrentFrequency(0);
|
||||||
mCurrentFrequency = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -347,11 +345,11 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
private void updateRestChannel(DMRChannel restChannel)
|
private void updateRestChannel(DMRChannel restChannel)
|
||||||
{
|
{
|
||||||
//Only respond if this is a standard/control channel (not a traffic channel).
|
//Only respond if this is a standard/control channel (not a traffic channel).
|
||||||
if(mChannel.isStandardChannel() && mCurrentFrequency > 0 &&
|
if(mChannel.isStandardChannel() && getCurrentFrequency() > 0 &&
|
||||||
restChannel.getDownlinkFrequency() > 0 &&
|
restChannel.getDownlinkFrequency() > 0 &&
|
||||||
restChannel.getDownlinkFrequency() != mCurrentFrequency && mTrafficChannelManager != null)
|
restChannel.getDownlinkFrequency() != getCurrentFrequency() && mTrafficChannelManager != null)
|
||||||
{
|
{
|
||||||
mTrafficChannelManager.convertToTrafficChannel(mChannel, mCurrentFrequency, restChannel,
|
mTrafficChannelManager.convertToTrafficChannel(mChannel, getCurrentFrequency(), restChannel,
|
||||||
mNetworkConfigurationMonitor);
|
mNetworkConfigurationMonitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1400,9 +1398,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
Event event = (mCurrentCallEvent == null ? Event.START : Event.CONTINUATION);
|
Event event = (mCurrentCallEvent == null ? Event.START : Event.CONTINUATION);
|
||||||
|
|
||||||
//Create a repeater channel descriptor if we don't have one
|
//Create a repeater channel descriptor if we don't have one
|
||||||
if(mCurrentChannel == null && mCurrentFrequency > 0)
|
if(mCurrentChannel == null && getCurrentFrequency() > 0)
|
||||||
{
|
{
|
||||||
mCurrentChannel = new DMRAbsoluteChannel(getTimeslot(), getTimeslot(), mCurrentFrequency, 0);
|
mCurrentChannel = new DMRAbsoluteChannel(getTimeslot(), getTimeslot(), getCurrentFrequency(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mCurrentCallEvent == null)
|
if(mCurrentCallEvent == null)
|
||||||
@@ -1475,11 +1473,12 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
resetState();
|
resetState();
|
||||||
break;
|
break;
|
||||||
case NOTIFICATION_SOURCE_FREQUENCY:
|
case NOTIFICATION_SOURCE_FREQUENCY:
|
||||||
long previous = mCurrentFrequency;
|
setCurrentFrequency(event.getFrequency());
|
||||||
mCurrentFrequency = event.getFrequency();
|
|
||||||
if(hasTrafficChannelManager())
|
//Only update the traffic channel manager if we're not a traffic channel.
|
||||||
|
if(hasTrafficChannelManager() && mChannel.isStandardChannel())
|
||||||
{
|
{
|
||||||
mTrafficChannelManager.setCurrentControlFrequency(previous, mCurrentFrequency, mChannel);
|
mTrafficChannelManager.setCurrentControlFrequency(getCurrentFrequency(), mChannel);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -132,13 +132,13 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
/**
|
/**
|
||||||
* Sets the current parent control channel frequency so that channel grants for the current frequency do not
|
* Sets the current parent control channel frequency so that channel grants for the current frequency do not
|
||||||
* produce an additional traffic channel allocation.
|
* produce an additional traffic channel allocation.
|
||||||
* @param previousControlFrequency for the current control channel (to remove from allocated channels)
|
* @param previous for the current control channel (to remove from allocated channels)
|
||||||
* @param currentControlFrequency for current control channel (to add to allocated channels)
|
* @param current for current control channel (to add to allocated channels)
|
||||||
* @param channel for the current control channel
|
* @param channel for the current control channel
|
||||||
*/
|
*/
|
||||||
public void setCurrentControlFrequency(long previousControlFrequency, long currentControlFrequency, Channel channel)
|
public void processControlFrequencyUpdate(long previous, long current, Channel channel)
|
||||||
{
|
{
|
||||||
if(previousControlFrequency == currentControlFrequency)
|
if(previous == current)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,18 +147,18 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Channel existing = mAllocatedChannelFrequencyMap.get(previousControlFrequency);
|
Channel existing = mAllocatedChannelFrequencyMap.get(previous);
|
||||||
|
|
||||||
//Only remove the channel if it is non-null and it matches the current control channel.
|
//Only remove the channel if it is non-null and it matches the current control channel.
|
||||||
if(channel.equals(existing))
|
if(channel.equals(existing))
|
||||||
{
|
{
|
||||||
//Unlock the frequency in the channel rotation monitor
|
//Unlock the frequency in the channel rotation monitor
|
||||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(previousControlFrequency));
|
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(previous));
|
||||||
mAllocatedChannelFrequencyMap.remove(previousControlFrequency);
|
mAllocatedChannelFrequencyMap.remove(previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
mAllocatedChannelFrequencyMap.put(currentControlFrequency, channel);
|
mAllocatedChannelFrequencyMap.put(current, channel);
|
||||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(currentControlFrequency));
|
getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(current));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,6 +23,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
|||||||
import io.github.dsheirer.module.decode.DecoderType;
|
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.module.decode.dmr.message.DMRMessage;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -64,10 +65,7 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
|||||||
@Override
|
@Override
|
||||||
public int[] getTimeslots()
|
public int[] getTimeslots()
|
||||||
{
|
{
|
||||||
int[] timeslots = new int[2];
|
return new int[]{DMRMessage.TIMESLOT_1, DMRMessage.TIMESLOT_2};
|
||||||
timeslots[0] = 1;
|
|
||||||
timeslots[1] = 2;
|
|
||||||
return timeslots;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,9 @@ import io.github.dsheirer.protocol.Protocol;
|
|||||||
*/
|
*/
|
||||||
public abstract class DMRMessage extends Message
|
public abstract class DMRMessage extends Message
|
||||||
{
|
{
|
||||||
|
public static final int TIMESLOT_1 = 1;
|
||||||
|
public static final int TIMESLOT_2 = 2;
|
||||||
|
|
||||||
private CorrectedBinaryMessage mCorrectedBinaryMessage;
|
private CorrectedBinaryMessage mCorrectedBinaryMessage;
|
||||||
private boolean mValid = true;
|
private boolean mValid = true;
|
||||||
private int mTimeslot;
|
private int mTimeslot;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,6 +50,28 @@ public class DecodeEvent implements IDecodeEvent
|
|||||||
mTimeStart = start;
|
mTimeStart = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets or changes the decode event type.
|
||||||
|
* @param type to set.
|
||||||
|
*/
|
||||||
|
public void setDecodeEventType(DecodeEventType type)
|
||||||
|
{
|
||||||
|
if(type == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Decode event type cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
mDecodeEventType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a snapshot of this decode event for offline analysis
|
||||||
|
*/
|
||||||
|
public DecodeEventSnapshot getSnapshot()
|
||||||
|
{
|
||||||
|
return new DecodeEventSnapshot(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new decode event builder with the specified start timestamp.
|
* Creates a new decode event builder with the specified start timestamp.
|
||||||
* @param decodeEventType for the event
|
* @param decodeEventType for the event
|
||||||
@@ -238,7 +260,8 @@ public class DecodeEvent implements IDecodeEvent
|
|||||||
sb.append(" IDS:").append(Joiner.on(",").join(mIdentifierCollection.getIdentifiers()));
|
sb.append(" IDS:").append(Joiner.on(",").join(mIdentifierCollection.getIdentifiers()));
|
||||||
}
|
}
|
||||||
sb.append(" DURATION:").append(getDuration());
|
sb.append(" DURATION:").append(getDuration());
|
||||||
sb.append(" CHANNEL:").append(mChannelDescriptor);
|
sb.append(" CHANNEL:").append(getChannelDescriptor());
|
||||||
|
sb.append(" TIMESLOT:").append(getTimeslot());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.event;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides duplicate decode event detection support
|
||||||
|
*/
|
||||||
|
public class DecodeEventDuplicateDetector
|
||||||
|
{
|
||||||
|
private static final long EVENT_MAX_AGE_MILLISECONDS = Duration.ofMinutes(1).toMillis();
|
||||||
|
private Map<DecodeEventType,DecodeEventTracker> mTrackerMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the event is a duplicate event.
|
||||||
|
* @param event to check
|
||||||
|
* @param timestamp of a current message to trigger time-based event age off.
|
||||||
|
* @return true if the event is a duplicate.
|
||||||
|
*/
|
||||||
|
public synchronized boolean isDuplicate(IDecodeEvent event, long timestamp)
|
||||||
|
{
|
||||||
|
//Null event types and voice call event types are not tracked by this detector
|
||||||
|
if(event.getEventType() == null || event.getEventType().isVoiceCallEvent())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mTrackerMap.containsKey(event.getEventType()))
|
||||||
|
{
|
||||||
|
mTrackerMap.put(event.getEventType(), new DecodeEventTracker());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean duplicate = mTrackerMap.get(event.getEventType()).isDuplicate(event);
|
||||||
|
|
||||||
|
for(DecodeEventTracker tracker: mTrackerMap.values())
|
||||||
|
{
|
||||||
|
tracker.ageOff(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return duplicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks all decode events for a given decode event type
|
||||||
|
*/
|
||||||
|
private class DecodeEventTracker
|
||||||
|
{
|
||||||
|
private Map<String,IDecodeEvent> mDecodeEventMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the event for duplicate
|
||||||
|
* @param event to check
|
||||||
|
* @return true if duplicate
|
||||||
|
*/
|
||||||
|
public boolean isDuplicate(IDecodeEvent event)
|
||||||
|
{
|
||||||
|
if(event.getIdentifierCollection().getToIdentifier() == null || event.getDetails() == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = getKey(event);
|
||||||
|
|
||||||
|
if(key == null || key.isEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mDecodeEventMap.containsKey(key))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mDecodeEventMap.put(key, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a unique event key that is a combinatio of the TO identifier value and the details of the event.
|
||||||
|
* @param event for the key
|
||||||
|
* @return generated key.
|
||||||
|
*/
|
||||||
|
private static String getKey(IDecodeEvent event)
|
||||||
|
{
|
||||||
|
return event.getIdentifierCollection().getToIdentifier().toString() + event.getDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all decode events from the duplicate detection map that are too old.
|
||||||
|
* @param timestamp for the current messaging.
|
||||||
|
*/
|
||||||
|
public void ageOff(long timestamp)
|
||||||
|
{
|
||||||
|
long threshold = timestamp - EVENT_MAX_AGE_MILLISECONDS;
|
||||||
|
List<String> toRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
for(Map.Entry<String,IDecodeEvent> entry: mDecodeEventMap.entrySet())
|
||||||
|
{
|
||||||
|
if(entry.getValue() == null || entry.getValue().getTimeStart() < threshold)
|
||||||
|
{
|
||||||
|
toRemove.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(String key: toRemove)
|
||||||
|
{
|
||||||
|
mDecodeEventMap.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -115,9 +115,9 @@ public class DecodeEventModel extends ClearableHistoryModel<IDecodeEvent> implem
|
|||||||
|
|
||||||
if(channelDescriptor != null)
|
if(channelDescriptor != null)
|
||||||
{
|
{
|
||||||
if(event.hasTimeslot())
|
if(event.hasTimeslot() && !event.toString().contains("TS"))
|
||||||
{
|
{
|
||||||
return channelDescriptor + " TS:" + event.getTimeslot();
|
return channelDescriptor + " TS" + event.getTimeslot();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -126,9 +126,9 @@ public class DecodeEventModel extends ClearableHistoryModel<IDecodeEvent> implem
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(event.hasTimeslot())
|
if(event.hasTimeslot() && !event.toString().contains("TS"))
|
||||||
{
|
{
|
||||||
return "TS:" + event.getTimeslot();
|
return "TS" + event.getTimeslot();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package io.github.dsheirer.module.decode.event;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snapshot of a decode event that also preserves the object instance hash code.
|
||||||
|
*/
|
||||||
|
public class DecodeEventSnapshot extends DecodeEvent
|
||||||
|
{
|
||||||
|
private int mOriginalHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param decodeEvent to snapshot
|
||||||
|
*/
|
||||||
|
public DecodeEventSnapshot(DecodeEvent decodeEvent)
|
||||||
|
{
|
||||||
|
super(decodeEvent.getEventType(), decodeEvent.getTimeStart());
|
||||||
|
mOriginalHashCode = decodeEvent.hashCode();
|
||||||
|
setDetails(decodeEvent.getDetails());
|
||||||
|
setDuration(decodeEvent.getDuration());
|
||||||
|
setProtocol(decodeEvent.getProtocol());
|
||||||
|
setTimeslot(decodeEvent.getTimeslot());
|
||||||
|
setChannelDescriptor(decodeEvent.getChannelDescriptor());
|
||||||
|
if(decodeEvent.getIdentifierCollection() != null)
|
||||||
|
{
|
||||||
|
setIdentifierCollection(new IdentifierCollection(decodeEvent.getIdentifierCollection().getIdentifiers()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hashcode of the original decode event for this snapshot (not this snapshot instance's hash code).
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getOriginalHashCode()
|
||||||
|
{
|
||||||
|
return mOriginalHashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency for the channel descriptor
|
||||||
|
* @return frequency or zero.
|
||||||
|
*/
|
||||||
|
public long getFrequency()
|
||||||
|
{
|
||||||
|
return getChannelDescriptor() != null ? getChannelDescriptor().getDownlinkFrequency() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -55,6 +55,7 @@ public enum DecodeEventType
|
|||||||
DATA_CALL_ENCRYPTED("Encrypted Data Call"),
|
DATA_CALL_ENCRYPTED("Encrypted Data Call"),
|
||||||
DATA_PACKET("Data Packet"),
|
DATA_PACKET("Data Packet"),
|
||||||
DEREGISTER("Deregister"),
|
DEREGISTER("Deregister"),
|
||||||
|
DYNAMIC_REGROUP("Dynamic Regroup"),
|
||||||
EMERGENCY("EMERGENCY"),
|
EMERGENCY("EMERGENCY"),
|
||||||
FUNCTION("Function"),
|
FUNCTION("Function"),
|
||||||
GPS("GPS"),
|
GPS("GPS"),
|
||||||
@@ -106,7 +107,7 @@ public enum DecodeEventType
|
|||||||
public static final EnumSet<DecodeEventType> COMMANDS = EnumSet.of(DecodeEventType.ANNOUNCEMENT,
|
public static final EnumSet<DecodeEventType> COMMANDS = EnumSet.of(DecodeEventType.ANNOUNCEMENT,
|
||||||
DecodeEventType.STATION_ID, DecodeEventType.ACKNOWLEDGE, DecodeEventType.PAGE, DecodeEventType.QUERY,
|
DecodeEventType.STATION_ID, DecodeEventType.ACKNOWLEDGE, DecodeEventType.PAGE, DecodeEventType.QUERY,
|
||||||
DecodeEventType.RADIO_CHECK, DecodeEventType.STATUS, DecodeEventType.COMMAND, DecodeEventType.EMERGENCY,
|
DecodeEventType.RADIO_CHECK, DecodeEventType.STATUS, DecodeEventType.COMMAND, DecodeEventType.EMERGENCY,
|
||||||
DecodeEventType.NOTIFICATION, DecodeEventType.FUNCTION);
|
DecodeEventType.NOTIFICATION, DecodeEventType.FUNCTION, DecodeEventType.DYNAMIC_REGROUP);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data call event types for filtering
|
* Data call event types for filtering
|
||||||
@@ -129,6 +130,13 @@ public enum DecodeEventType
|
|||||||
public static final EnumSet<DecodeEventType> OTHERS = EnumSet.copyOf(Arrays.stream(DecodeEventType.values())
|
public static final EnumSet<DecodeEventType> OTHERS = EnumSet.copyOf(Arrays.stream(DecodeEventType.values())
|
||||||
.filter(decodeEventType -> !decodeEventType.isGrouped()).toList());
|
.filter(decodeEventType -> !decodeEventType.isGrouped()).toList());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Voice call event types.
|
||||||
|
*/
|
||||||
|
public static final EnumSet<DecodeEventType> VOICE_CALL_EVENTS = EnumSet.of(CALL, CALL_ENCRYPTED, CALL_GROUP,
|
||||||
|
CALL_GROUP_ENCRYPTED, CALL_PATCH_GROUP, CALL_PATCH_GROUP_ENCRYPTED, CALL_INTERCONNECT,
|
||||||
|
CALL_INTERCONNECT_ENCRYPTED, CALL_UNIT_TO_UNIT, CALL_UNIT_TO_UNIT_ENCRYPTED);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param label for the element
|
* @param label for the element
|
||||||
@@ -149,12 +157,12 @@ public enum DecodeEventType
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this is a call event.
|
* Indicates if this is a voice call event.
|
||||||
* @return true if this is a call event type.
|
* @return true if this is a voice call event type.
|
||||||
*/
|
*/
|
||||||
public boolean isCallEvent()
|
public boolean isVoiceCallEvent()
|
||||||
{
|
{
|
||||||
return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || DATA_CALLS.contains(this);
|
return VOICE_CALL_EVENTS.contains(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,7 @@ public class UDPPort extends IntegerIdentifier
|
|||||||
*/
|
*/
|
||||||
public UDPPort(int value, Role role)
|
public UDPPort(int value, Role role)
|
||||||
{
|
{
|
||||||
super(value, IdentifierClass.NETWORK, Form.UDP_PORT, role);
|
super(value, IdentifierClass.USER_NETWORK_PORT, Form.UDP_PORT, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -76,6 +76,32 @@ public class MPT1327TrafficChannelManager extends TrafficChannelManager implemen
|
|||||||
mChannelMap = channelMap;
|
mChannelMap = channelMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification that the control frequency is updated.
|
||||||
|
* @param previous frequency for the control channel (to remove from allocated channels)
|
||||||
|
* @param current frequency for the control channel (to add to allocated channels)
|
||||||
|
* @param channel for the current control channel
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void processControlFrequencyUpdate(long previous, long current, Channel channel)
|
||||||
|
{
|
||||||
|
MPT1327Channel toRemove = null;
|
||||||
|
|
||||||
|
for(MPT1327Channel mpt: mAllocatedTrafficChannelMap.keySet())
|
||||||
|
{
|
||||||
|
if(mpt.getDownlinkFrequency() == current)
|
||||||
|
{
|
||||||
|
toRemove = mpt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(toRemove != null)
|
||||||
|
{
|
||||||
|
broadcast(new ChannelEvent(mAllocatedTrafficChannelMap.get(toRemove), ChannelEvent.Event.REQUEST_DISABLE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes channel grants to allocate traffic channels and track overall channel usage. Generates
|
* Processes channel grants to allocate traffic channels and track overall channel usage. Generates
|
||||||
* decode events for each new channel that is allocated.
|
* decode events for each new channel that is allocated.
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package io.github.dsheirer.module.decode.p25;
|
||||||
|
|
||||||
|
|
||||||
|
import io.github.dsheirer.module.decode.p25.reference.ServiceOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APCO25 message that exposes a service options configuration
|
||||||
|
*/
|
||||||
|
public interface IServiceOptionsProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Service Options
|
||||||
|
* @return service options
|
||||||
|
*/
|
||||||
|
ServiceOptions getServiceOptions();
|
||||||
|
}
|
||||||
@@ -117,7 +117,7 @@ public class P25ChannelGrantEvent extends P25DecodeEvent
|
|||||||
* Sets the channel descriptor for this event
|
* Sets the channel descriptor for this event
|
||||||
* @param channelDescriptor
|
* @param channelDescriptor
|
||||||
*/
|
*/
|
||||||
public P25ChannelGrantDecodeEventBuilder channel(IChannelDescriptor channelDescriptor)
|
public P25ChannelGrantDecodeEventBuilder channelDescriptor(IChannelDescriptor channelDescriptor)
|
||||||
{
|
{
|
||||||
mChannelDescriptor = channelDescriptor;
|
mChannelDescriptor = channelDescriptor;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package io.github.dsheirer.module.decode.p25;
|
||||||
|
|
||||||
|
import io.github.dsheirer.controller.channel.event.PreloadDataContent;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency bands (aka Identifier Update) to preload into a traffic channel.
|
||||||
|
*/
|
||||||
|
public class P25FrequencyBandPreloadDataContent extends PreloadDataContent<Collection<IFrequencyBand>>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param data to preload
|
||||||
|
*/
|
||||||
|
public P25FrequencyBandPreloadDataContent(Collection<IFrequencyBand> data)
|
||||||
|
{
|
||||||
|
super(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,14 +23,14 @@ package io.github.dsheirer.module.decode.p25.audio;
|
|||||||
import io.github.dsheirer.audio.codec.mbe.MBECallSequence;
|
import io.github.dsheirer.audio.codec.mbe.MBECallSequence;
|
||||||
import io.github.dsheirer.audio.codec.mbe.MBECallSequenceRecorder;
|
import io.github.dsheirer.audio.codec.mbe.MBECallSequenceRecorder;
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
|
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData;
|
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData;
|
||||||
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUpdate;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdateExplicit;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdateExplicit;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectVoiceChannelUser;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectVoiceChannelUser;
|
||||||
@@ -86,9 +86,9 @@ public class P25P1CallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
@Override
|
@Override
|
||||||
public void receive(IMessage message)
|
public void receive(IMessage message)
|
||||||
{
|
{
|
||||||
if(message instanceof P25Message)
|
if(message instanceof P25P1Message)
|
||||||
{
|
{
|
||||||
P25Message p25 = (P25Message)message;
|
P25P1Message p25 = (P25P1Message)message;
|
||||||
|
|
||||||
if(p25.isValid())
|
if(p25.isValid())
|
||||||
{
|
{
|
||||||
@@ -113,7 +113,7 @@ public class P25P1CallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
/**
|
/**
|
||||||
* Processes any P25 Phase 1 message
|
* Processes any P25 Phase 1 message
|
||||||
*/
|
*/
|
||||||
public void process(P25Message message)
|
public void process(P25P1Message message)
|
||||||
{
|
{
|
||||||
if(message instanceof LDUMessage)
|
if(message instanceof LDUMessage)
|
||||||
{
|
{
|
||||||
@@ -200,15 +200,15 @@ public class P25P1CallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
mCallSequence.setToIdentifier(tivcu.getAddress().toString());
|
mCallSequence.setToIdentifier(tivcu.getAddress().toString());
|
||||||
mCallSequence.setCallType(CALL_TYPE_TELEPHONE_INTERCONNECT);
|
mCallSequence.setCallType(CALL_TYPE_TELEPHONE_INTERCONNECT);
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER:
|
case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER:
|
||||||
LCMotorolaPatchGroupVoiceChannelUser mpgvcu = (LCMotorolaPatchGroupVoiceChannelUser)lcw;
|
LCMotorolaGroupRegroupVoiceChannelUser mpgvcu = (LCMotorolaGroupRegroupVoiceChannelUser)lcw;
|
||||||
mCallSequence.setFromIdentifier(mpgvcu.getSourceAddress().toString());
|
mCallSequence.setFromIdentifier(mpgvcu.getSourceAddress().toString());
|
||||||
mCallSequence.setToIdentifier(mpgvcu.getGroupAddress().toString());
|
mCallSequence.setToIdentifier(mpgvcu.getSupergroupAddress().toString());
|
||||||
mCallSequence.setCallType(CALL_TYPE_GROUP);
|
mCallSequence.setCallType(CALL_TYPE_GROUP);
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE:
|
case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE:
|
||||||
LCMotorolaPatchGroupVoiceChannelUpdate mpgvcup = (LCMotorolaPatchGroupVoiceChannelUpdate)lcw;
|
LCMotorolaGroupRegroupVoiceChannelUpdate mpgvcup = (LCMotorolaGroupRegroupVoiceChannelUpdate)lcw;
|
||||||
mCallSequence.setToIdentifier(mpgvcup.getPatchGroup().toString());
|
mCallSequence.setToIdentifier(mpgvcup.getSupergroupAddress().toString());
|
||||||
mCallSequence.setCallType(CALL_TYPE_GROUP);
|
mCallSequence.setCallType(CALL_TYPE_GROUP);
|
||||||
break;
|
break;
|
||||||
case CALL_TERMINATION_OR_CANCELLATION:
|
case CALL_TERMINATION_OR_CANCELLATION:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,6 +36,8 @@ import io.github.dsheirer.identifier.tone.ToneSequence;
|
|||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.message.IMessageProvider;
|
import io.github.dsheirer.message.IMessageProvider;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
|
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
@@ -111,10 +113,8 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
{
|
{
|
||||||
if(message.getTimeslot() == getTimeslot())
|
if(message.getTimeslot() == getTimeslot())
|
||||||
{
|
{
|
||||||
if(message instanceof AbstractVoiceTimeslot)
|
if(message instanceof AbstractVoiceTimeslot abstractVoiceTimeslot)
|
||||||
{
|
{
|
||||||
AbstractVoiceTimeslot abstractVoiceTimeslot = (AbstractVoiceTimeslot)message;
|
|
||||||
|
|
||||||
if(mEncryptedCallStateEstablished)
|
if(mEncryptedCallStateEstablished)
|
||||||
{
|
{
|
||||||
if(!mEncryptedCall)
|
if(!mEncryptedCall)
|
||||||
@@ -128,13 +128,18 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
mQueuedAudioTimeslots.offer(abstractVoiceTimeslot);
|
mQueuedAudioTimeslots.offer(abstractVoiceTimeslot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(message instanceof PushToTalk && message.isValid())
|
else if(message instanceof MacMessage macMessage && message.isValid())
|
||||||
{
|
{
|
||||||
mEncryptedCallStateEstablished = true;
|
MacStructure macStructure = macMessage.getMacStructure();
|
||||||
mEncryptedCall = ((PushToTalk)message).isEncrypted();
|
|
||||||
|
|
||||||
//There should not be any pending voice timeslots to process since the PTT message is the first in
|
if(macStructure instanceof PushToTalk pushToTalk)
|
||||||
//the audio call sequence
|
{
|
||||||
|
mEncryptedCallStateEstablished = true;
|
||||||
|
mEncryptedCall = pushToTalk.isEncrypted();
|
||||||
|
//There should not be any pending voice timeslots to process since the PTT message is the first in
|
||||||
|
//the audio call sequence.
|
||||||
|
clearPendingVoiceTimeslots();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(message instanceof EncryptionSynchronizationSequence && message.isValid())
|
else if(message instanceof EncryptionSynchronizationSequence && message.isValid())
|
||||||
{
|
{
|
||||||
@@ -159,6 +164,14 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears/deletes any pending voice timeslots
|
||||||
|
*/
|
||||||
|
private void clearPendingVoiceTimeslots()
|
||||||
|
{
|
||||||
|
mQueuedAudioTimeslots.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the audio voice frames
|
* Process the audio voice frames
|
||||||
* @param voiceFrames to process
|
* @param voiceFrames to process
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.p25.audio;
|
package io.github.dsheirer.module.decode.p25.audio;
|
||||||
@@ -30,21 +27,20 @@ import io.github.dsheirer.message.IMessage;
|
|||||||
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message;
|
import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.EndPushToTalk;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.EndPushToTalk;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserAbbreviated;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserAbbreviated;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserExtended;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.GroupVoiceChannelUserExtended;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructure;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.PushToTalk;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelUser;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.TelephoneInterconnectVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserAbbreviated;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserAbbreviated;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended;
|
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.UnitToUnitVoiceChannelUserExtended;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
|
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P25 Phase 2 AMBE Frame recorder generates P25 call sequence recordings containing JSON representations of audio
|
* P25 Phase 2 AMBE Frame recorder generates P25 call sequence recordings containing JSON representations of audio
|
||||||
* frames, optional encryption and call identifiers.
|
* frames, optional encryption and call identifiers.
|
||||||
@@ -229,19 +225,19 @@ public class P25P2CallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
case END_PUSH_TO_TALK:
|
case END_PUSH_TO_TALK:
|
||||||
processEndPTT(mac);
|
processEndPTT(mac);
|
||||||
break;
|
break;
|
||||||
case TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED:
|
case TDMA_01_GROUP_VOICE_CHANNEL_USER_ABBREVIATED:
|
||||||
processGVCUA(mac);
|
processGVCUA(mac);
|
||||||
break;
|
break;
|
||||||
case TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER:
|
case TDMA_02_UNIT_TO_UNIT_VOICE_CHANNEL_USER_ABBREVIATED:
|
||||||
processUTUVCU(mac);
|
processUTUVCU(mac);
|
||||||
break;
|
break;
|
||||||
case TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
|
case TDMA_03_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
|
||||||
processTIVCU(mac);
|
processTIVCU(mac);
|
||||||
break;
|
break;
|
||||||
case TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED:
|
case TDMA_21_GROUP_VOICE_CHANNEL_USER_EXTENDED:
|
||||||
processGVCUE(mac);
|
processGVCUE(mac);
|
||||||
break;
|
break;
|
||||||
case TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED:
|
case TDMA_22_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED:
|
||||||
processUTUVCUE(mac);
|
processUTUVCUE(mac);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -258,7 +254,7 @@ public class P25P2CallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
if(mac instanceof UnitToUnitVoiceChannelUserExtended)
|
if(mac instanceof UnitToUnitVoiceChannelUserExtended)
|
||||||
{
|
{
|
||||||
UnitToUnitVoiceChannelUserExtended uuvcue = (UnitToUnitVoiceChannelUserExtended)mac;
|
UnitToUnitVoiceChannelUserExtended uuvcue = (UnitToUnitVoiceChannelUserExtended)mac;
|
||||||
mCallSequence.setFromIdentifier(uuvcue.getSourceAddress().toString());
|
mCallSequence.setFromIdentifier(uuvcue.getSource().toString());
|
||||||
mCallSequence.setToIdentifier(uuvcue.getTargetAddress().toString());
|
mCallSequence.setToIdentifier(uuvcue.getTargetAddress().toString());
|
||||||
mCallSequence.setCallType(CALL_TYPE_INDIVIDUAL);
|
mCallSequence.setCallType(CALL_TYPE_INDIVIDUAL);
|
||||||
if(uuvcue.getServiceOptions().isEncrypted())
|
if(uuvcue.getServiceOptions().isEncrypted())
|
||||||
@@ -276,7 +272,7 @@ public class P25P2CallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
if(mac instanceof GroupVoiceChannelUserExtended)
|
if(mac instanceof GroupVoiceChannelUserExtended)
|
||||||
{
|
{
|
||||||
GroupVoiceChannelUserExtended gvcue = (GroupVoiceChannelUserExtended)mac;
|
GroupVoiceChannelUserExtended gvcue = (GroupVoiceChannelUserExtended)mac;
|
||||||
mCallSequence.setFromIdentifier(gvcue.getSourceAddress().toString());
|
mCallSequence.setFromIdentifier(gvcue.getSource().toString());
|
||||||
mCallSequence.setToIdentifier(gvcue.getGroupAddress().toString());
|
mCallSequence.setToIdentifier(gvcue.getGroupAddress().toString());
|
||||||
mCallSequence.setCallType(CALL_TYPE_GROUP);
|
mCallSequence.setCallType(CALL_TYPE_GROUP);
|
||||||
if(gvcue.getServiceOptions().isEncrypted())
|
if(gvcue.getServiceOptions().isEncrypted())
|
||||||
@@ -294,7 +290,7 @@ public class P25P2CallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
if(mac instanceof TelephoneInterconnectVoiceChannelUser)
|
if(mac instanceof TelephoneInterconnectVoiceChannelUser)
|
||||||
{
|
{
|
||||||
TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)mac;
|
TelephoneInterconnectVoiceChannelUser tivcu = (TelephoneInterconnectVoiceChannelUser)mac;
|
||||||
mCallSequence.setToIdentifier(tivcu.getToOrFromAddress().toString());
|
mCallSequence.setToIdentifier(tivcu.getTargetAddress().toString());
|
||||||
mCallSequence.setCallType(CALL_TYPE_TELEPHONE_INTERCONNECT);
|
mCallSequence.setCallType(CALL_TYPE_TELEPHONE_INTERCONNECT);
|
||||||
if(tivcu.getServiceOptions().isEncrypted())
|
if(tivcu.getServiceOptions().isEncrypted())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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.p25.identifier;
|
package io.github.dsheirer.module.decode.p25.identifier;
|
||||||
|
|
||||||
@@ -46,4 +45,10 @@ public class APCO25Lra extends IntegerIdentifier
|
|||||||
{
|
{
|
||||||
return new APCO25Lra(lra);
|
return new APCO25Lra(lra);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getValue() + "/x" + String.format("%02X", getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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.p25.identifier;
|
package io.github.dsheirer.module.decode.p25.identifier;
|
||||||
|
|
||||||
@@ -52,4 +51,10 @@ public class APCO25Nac extends IntegerIdentifier
|
|||||||
{
|
{
|
||||||
return new APCO25Nac(nac);
|
return new APCO25Nac(nac);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getValue() + "/x" + String.format("%03X", getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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.p25.identifier;
|
package io.github.dsheirer.module.decode.p25.identifier;
|
||||||
|
|
||||||
@@ -46,4 +45,10 @@ public class APCO25Rfss extends IntegerIdentifier
|
|||||||
{
|
{
|
||||||
return new APCO25Rfss(rfss);
|
return new APCO25Rfss(rfss);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getValue() + "/x" + String.format("%02X", getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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,12 +14,11 @@
|
|||||||
*
|
*
|
||||||
* 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.p25.identifier;
|
package io.github.dsheirer.module.decode.p25.identifier;
|
||||||
|
|
||||||
import io.github.dsheirer.identifier.Form;
|
import io.github.dsheirer.identifier.Form;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
|
||||||
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.integer.IntegerIdentifier;
|
||||||
@@ -42,8 +40,14 @@ public class APCO25Site extends IntegerIdentifier
|
|||||||
/**
|
/**
|
||||||
* Creates a new APCO-25 site identifier
|
* Creates a new APCO-25 site identifier
|
||||||
*/
|
*/
|
||||||
public static Identifier create(int site)
|
public static IntegerIdentifier create(int site)
|
||||||
{
|
{
|
||||||
return new APCO25Site(site);
|
return new APCO25Site(site);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getValue() + "/x" + String.format("%02X", getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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.p25.identifier;
|
package io.github.dsheirer.module.decode.p25.identifier;
|
||||||
|
|
||||||
@@ -45,4 +44,11 @@ public class APCO25System extends IntegerIdentifier
|
|||||||
{
|
{
|
||||||
return new APCO25System(system);
|
return new APCO25System(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getValue() + "/x" + String.format("%03X", getValue());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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.p25.identifier;
|
package io.github.dsheirer.module.decode.p25.identifier;
|
||||||
|
|
||||||
@@ -46,4 +45,11 @@ public class APCO25Wacn extends IntegerIdentifier
|
|||||||
{
|
{
|
||||||
return new APCO25Wacn(wacn);
|
return new APCO25Wacn(wacn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getValue() + "/x" + String.format("%05X", getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.p25.identifier.channel;
|
package io.github.dsheirer.module.decode.p25.identifier.channel;
|
||||||
@@ -25,7 +22,6 @@ package io.github.dsheirer.module.decode.p25.identifier.channel;
|
|||||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class P25Channel implements IChannelDescriptor
|
public class P25Channel implements IChannelDescriptor
|
||||||
@@ -151,17 +147,24 @@ public class P25Channel implements IChannelDescriptor
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timeslot for a TDMA channel
|
* Timeslot for a TDMA channel, 1 or 2
|
||||||
* @return timeslot or 0 if the channel is not a TDMA channel
|
*
|
||||||
|
* Note: the ICD uses Logical Channel 0 (LCH 0) and Logical Channel 1 (LCH 1) but we refer to them as TS1 and TS2
|
||||||
|
* so that timeslot 0 can be used for non timeslot signalling like Sync Loss messages.
|
||||||
|
*
|
||||||
|
* @return timeslot or 1 if the channel is not a TDMA channel
|
||||||
*/
|
*/
|
||||||
public int getTimeslot()
|
public int getTimeslot()
|
||||||
{
|
{
|
||||||
if(isTDMAChannel())
|
if(isTDMAChannel())
|
||||||
{
|
{
|
||||||
return getDownlinkChannelNumber() % getTimeslotCount();
|
if(getDownlinkChannelNumber() % getTimeslotCount() == 1)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.identifier.channel;
|
package io.github.dsheirer.module.decode.p25.identifier.channel;
|
||||||
|
|
||||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,7 +29,7 @@ import java.text.DecimalFormat;
|
|||||||
*/
|
*/
|
||||||
public class StandardChannel implements IChannelDescriptor
|
public class StandardChannel implements IChannelDescriptor
|
||||||
{
|
{
|
||||||
private static final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat("0.000");
|
private static final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat("0.0000");
|
||||||
private long mFrequency;
|
private long mFrequency;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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,12 +14,13 @@
|
|||||||
*
|
*
|
||||||
* 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.p25.identifier.patch;
|
package io.github.dsheirer.module.decode.p25.identifier.patch;
|
||||||
|
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,4 +43,9 @@ public class APCO25PatchGroup extends PatchGroupIdentifier
|
|||||||
{
|
{
|
||||||
return new APCO25PatchGroup(patchGroup);
|
return new APCO25PatchGroup(patchGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static APCO25PatchGroup create(int supergroup)
|
||||||
|
{
|
||||||
|
return new APCO25PatchGroup(new PatchGroup(APCO25Talkgroup.create(supergroup)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.p25.identifier.radio;
|
package io.github.dsheirer.module.decode.p25.identifier.radio;
|
||||||
@@ -31,9 +28,17 @@ import io.github.dsheirer.protocol.Protocol;
|
|||||||
*/
|
*/
|
||||||
public class APCO25FullyQualifiedRadioIdentifier extends FullyQualifiedRadioIdentifier
|
public class APCO25FullyQualifiedRadioIdentifier extends FullyQualifiedRadioIdentifier
|
||||||
{
|
{
|
||||||
public APCO25FullyQualifiedRadioIdentifier(int wacn, int system, int id, Role role)
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param localAddress radio identifier. This can be the same as the radio ID when the fully qualified radio
|
||||||
|
* is not being aliased on a local radio system.
|
||||||
|
* @param wacn of the home network for the radio.
|
||||||
|
* @param system of the home network for the radio.
|
||||||
|
* @param id of the radio within the home network.
|
||||||
|
*/
|
||||||
|
public APCO25FullyQualifiedRadioIdentifier(int localAddress, int wacn, int system, int id, Role role)
|
||||||
{
|
{
|
||||||
super(wacn, system, id, role);
|
super(localAddress, wacn, system, id, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,13 +47,31 @@ public class APCO25FullyQualifiedRadioIdentifier extends FullyQualifiedRadioIden
|
|||||||
return Protocol.APCO25;
|
return Protocol.APCO25;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static APCO25FullyQualifiedRadioIdentifier createFrom(int wacn, int system, int id)
|
/**
|
||||||
|
* Creates a fully qualified radio and assigns the FROM role.
|
||||||
|
* @param localAddress radio identifier. This can be the same as the radio ID when the fully qualified radio
|
||||||
|
* is not being aliased on a local radio system.
|
||||||
|
* @param wacn of the home network for the radio.
|
||||||
|
* @param system of the home network for the radio.
|
||||||
|
* @param id of the radio within the home network.
|
||||||
|
* @return identifier
|
||||||
|
*/
|
||||||
|
public static APCO25FullyQualifiedRadioIdentifier createFrom(int localAddress, int wacn, int system, int id)
|
||||||
{
|
{
|
||||||
return new APCO25FullyQualifiedRadioIdentifier(wacn, system, id, Role.FROM);
|
return new APCO25FullyQualifiedRadioIdentifier(localAddress, wacn, system, id, Role.FROM);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static APCO25FullyQualifiedRadioIdentifier createTo(int wacn, int system, int id)
|
/**
|
||||||
|
* Creates a fully qualified radio and assigns the TO role.
|
||||||
|
* @param localAddress radio identifier. This can be the same as the radio ID when the fully qualified radio
|
||||||
|
* is not being aliased on a local radio system.
|
||||||
|
* @param wacn of the home network for the radio.
|
||||||
|
* @param system of the home network for the radio.
|
||||||
|
* @param id of the radio within the home network.
|
||||||
|
* @return identifier
|
||||||
|
*/
|
||||||
|
public static APCO25FullyQualifiedRadioIdentifier createTo(int localAddress, int wacn, int system, int id)
|
||||||
{
|
{
|
||||||
return new APCO25FullyQualifiedRadioIdentifier(wacn, system, id, Role.TO);
|
return new APCO25FullyQualifiedRadioIdentifier(localAddress, wacn, system, id, Role.TO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.p25.identifier.talkgroup;
|
package io.github.dsheirer.module.decode.p25.identifier.talkgroup;
|
||||||
|
|
||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,9 +32,17 @@ import io.github.dsheirer.protocol.Protocol;
|
|||||||
*/
|
*/
|
||||||
public class APCO25FullyQualifiedTalkgroupIdentifier extends FullyQualifiedTalkgroupIdentifier
|
public class APCO25FullyQualifiedTalkgroupIdentifier extends FullyQualifiedTalkgroupIdentifier
|
||||||
{
|
{
|
||||||
public APCO25FullyQualifiedTalkgroupIdentifier(int wacn, int system, int id, Role role)
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param groupAddress used on the local system as an alias to the fully qualified talkgroup.
|
||||||
|
* @param wacn for the talkgroup home system.
|
||||||
|
* @param system for the talkgroup home system.
|
||||||
|
* @param id for the talkgroup within the home system.
|
||||||
|
* @param role played by the talkgroup.
|
||||||
|
*/
|
||||||
|
public APCO25FullyQualifiedTalkgroupIdentifier(int groupAddress, int wacn, int system, int id, Role role)
|
||||||
{
|
{
|
||||||
super(wacn, system, id, role);
|
super(groupAddress, wacn, system, id, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -42,18 +51,39 @@ public class APCO25FullyQualifiedTalkgroupIdentifier extends FullyQualifiedTalkg
|
|||||||
return Protocol.APCO25;
|
return Protocol.APCO25;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static APCO25FullyQualifiedTalkgroupIdentifier createFrom(int wacn, int system, int id)
|
/**
|
||||||
|
* Creates an identifier for the fully qualified talkgroup ising the FROM role.
|
||||||
|
* @param groupAddress used on the local system as an alias to the fully qualified talkgroup.
|
||||||
|
* @param wacn for the talkgroup home system.
|
||||||
|
* @param system for the talkgroup home system.
|
||||||
|
* @param id for the talkgroup within the home system.
|
||||||
|
*/
|
||||||
|
public static APCO25FullyQualifiedTalkgroupIdentifier createFrom(int groupAddress, int wacn, int system, int id)
|
||||||
{
|
{
|
||||||
return new APCO25FullyQualifiedTalkgroupIdentifier(wacn, system, id, Role.FROM);
|
return new APCO25FullyQualifiedTalkgroupIdentifier(groupAddress, wacn, system, id, Role.FROM);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static APCO25FullyQualifiedTalkgroupIdentifier createTo(int wacn, int system, int id)
|
/**
|
||||||
|
* Creates an identifier for the fully qualified talkgroup ising the TO role.
|
||||||
|
* @param groupAddress used on the local system as an alias to the fully qualified talkgroup.
|
||||||
|
* @param wacn for the talkgroup home system.
|
||||||
|
* @param system for the talkgroup home system.
|
||||||
|
* @param id for the talkgroup within the home system.
|
||||||
|
*/
|
||||||
|
public static APCO25FullyQualifiedTalkgroupIdentifier createTo(int groupAddress, int wacn, int system, int id)
|
||||||
{
|
{
|
||||||
return new APCO25FullyQualifiedTalkgroupIdentifier(wacn, system, id, Role.TO);
|
return new APCO25FullyQualifiedTalkgroupIdentifier(groupAddress, wacn, system, id, Role.TO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static APCO25FullyQualifiedTalkgroupIdentifier createAny(int wacn, int system, int id)
|
/**
|
||||||
|
* Creates an identifier for the fully qualified talkgroup ising the ANY role.
|
||||||
|
* @param groupAddress used on the local system as an alias to the fully qualified talkgroup.
|
||||||
|
* @param wacn for the talkgroup home system.
|
||||||
|
* @param system for the talkgroup home system.
|
||||||
|
* @param id for the talkgroup within the home system.
|
||||||
|
*/
|
||||||
|
public static APCO25FullyQualifiedTalkgroupIdentifier createAny(int groupAddress, int wacn, int system, int id)
|
||||||
{
|
{
|
||||||
return new APCO25FullyQualifiedTalkgroupIdentifier(wacn, system, id, Role.ANY);
|
return new APCO25FullyQualifiedTalkgroupIdentifier(groupAddress, wacn, system, id, Role.ANY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
package io.github.dsheirer.module.decode.p25.phase1;
|
||||||
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
|
@JsonSubTypes({
|
||||||
|
@JsonSubTypes.Type(value = DecodeConfigP25Phase1.class, name = "decodeConfigP25Phase1"),
|
||||||
|
@JsonSubTypes.Type(value = DecodeConfigP25Phase2.class, name = "decodeConfigP25Phase2"),
|
||||||
|
})
|
||||||
|
public abstract class DecodeConfigP25 extends DecodeConfiguration
|
||||||
|
{
|
||||||
|
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||||
|
private boolean mIgnoreDataCalls = false;
|
||||||
|
|
||||||
|
public DecodeConfigP25()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "ignore_data_calls")
|
||||||
|
public boolean getIgnoreDataCalls()
|
||||||
|
{
|
||||||
|
return mIgnoreDataCalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIgnoreDataCalls(boolean ignore)
|
||||||
|
{
|
||||||
|
mIgnoreDataCalls = ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "traffic_channel_pool_size")
|
||||||
|
public int getTrafficChannelPoolSize()
|
||||||
|
{
|
||||||
|
return mTrafficChannelPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the traffic channel pool size which is the maximum number of
|
||||||
|
* simultaneous traffic channels that can be allocated.
|
||||||
|
*
|
||||||
|
* This limits the maximum calls so that busy systems won't cause more
|
||||||
|
* traffic channels to be allocated than the decoder/software/host computer
|
||||||
|
* can support.
|
||||||
|
*/
|
||||||
|
public void setTrafficChannelPoolSize(int size)
|
||||||
|
{
|
||||||
|
mTrafficChannelPoolSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.module.decode.p25.phase1;
|
package io.github.dsheirer.module.decode.p25.phase1;
|
||||||
|
|
||||||
@@ -25,10 +22,12 @@ package io.github.dsheirer.module.decode.p25.phase1;
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
import io.github.dsheirer.module.decode.DecoderType;
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
|
||||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
|
|
||||||
public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
/**
|
||||||
|
* APCO25 Phase 1 decoder configuration
|
||||||
|
*/
|
||||||
|
public class DecodeConfigP25Phase1 extends DecodeConfigP25
|
||||||
{
|
{
|
||||||
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 400;
|
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 400;
|
||||||
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
|
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
|
||||||
@@ -36,9 +35,9 @@ public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
|||||||
|
|
||||||
private P25P1Decoder.Modulation mModulation = P25P1Decoder.Modulation.C4FM;
|
private P25P1Decoder.Modulation mModulation = P25P1Decoder.Modulation.C4FM;
|
||||||
|
|
||||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
/**
|
||||||
private boolean mIgnoreDataCalls = false;
|
* Constructs an instance
|
||||||
|
*/
|
||||||
public DecodeConfigP25Phase1()
|
public DecodeConfigP25Phase1()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -60,37 +59,6 @@ public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
|||||||
mModulation = modulation;
|
mModulation = modulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "ignore_data_calls")
|
|
||||||
public boolean getIgnoreDataCalls()
|
|
||||||
{
|
|
||||||
return mIgnoreDataCalls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIgnoreDataCalls(boolean ignore)
|
|
||||||
{
|
|
||||||
mIgnoreDataCalls = ignore;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "traffic_channel_pool_size")
|
|
||||||
public int getTrafficChannelPoolSize()
|
|
||||||
{
|
|
||||||
return mTrafficChannelPoolSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the traffic channel pool size which is the maximum number of
|
|
||||||
* simultaneous traffic channels that can be allocated.
|
|
||||||
*
|
|
||||||
* This limits the maximum calls so that busy systems won't cause more
|
|
||||||
* traffic channels to be allocated than the decoder/software/host computer
|
|
||||||
* can support.
|
|
||||||
*/
|
|
||||||
public void setTrafficChannelPoolSize(int size)
|
|
||||||
{
|
|
||||||
mTrafficChannelPoolSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source channel specification for this decoder
|
* Source channel specification for this decoder
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,11 +18,16 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.p25.phase1;
|
package io.github.dsheirer.module.decode.p25.phase1;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import io.github.dsheirer.dsp.squelch.PowerMonitor;
|
import io.github.dsheirer.dsp.squelch.PowerMonitor;
|
||||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
|
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
import io.github.dsheirer.module.decode.DecoderType;
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
import io.github.dsheirer.module.decode.FeedbackDecoder;
|
||||||
|
import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent;
|
||||||
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.sample.buffer.IByteBufferProvider;
|
import io.github.dsheirer.sample.buffer.IByteBufferProvider;
|
||||||
@@ -52,6 +57,18 @@ public abstract class P25P1Decoder extends FeedbackDecoder implements ISourceEve
|
|||||||
getDibitBroadcaster().addListener(mByteBufferAssembler);
|
getDibitBroadcaster().addListener(mByteBufferAssembler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preloads P25 frequency bands when this is a traffic channel, since they are not transmitted on the traffic
|
||||||
|
* channel and the decoder state needs accurate frequency values to pass to the traffic channel manager for
|
||||||
|
* call event tracking.
|
||||||
|
* @param preLoadDataContent with known frequency bands.
|
||||||
|
*/
|
||||||
|
@Subscribe
|
||||||
|
public void process(P25FrequencyBandPreloadDataContent preLoadDataContent)
|
||||||
|
{
|
||||||
|
mMessageProcessor.preload(preLoadDataContent);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSourceEventListener(Listener<SourceEvent> listener )
|
public void setSourceEventListener(Listener<SourceEvent> listener )
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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
|
||||||
@@ -30,7 +30,6 @@ import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
|||||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.message.Message;
|
|
||||||
import io.github.dsheirer.message.MessageProviderModule;
|
import io.github.dsheirer.message.MessageProviderModule;
|
||||||
import io.github.dsheirer.message.StuffBitsMessage;
|
import io.github.dsheirer.message.StuffBitsMessage;
|
||||||
import io.github.dsheirer.message.SyncLossMessage;
|
import io.github.dsheirer.message.SyncLossMessage;
|
||||||
@@ -40,8 +39,8 @@ import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
|||||||
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
|
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
||||||
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25MessageFactory;
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25MessageFactory;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessageFactory;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessageFactory;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage;
|
||||||
@@ -73,7 +72,7 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
|
|||||||
|
|
||||||
private P25P1DataUnitDetector mDataUnitDetector;
|
private P25P1DataUnitDetector mDataUnitDetector;
|
||||||
private P25P1ChannelStatusProcessor mChannelStatusProcessor = new P25P1ChannelStatusProcessor();
|
private P25P1ChannelStatusProcessor mChannelStatusProcessor = new P25P1ChannelStatusProcessor();
|
||||||
private Listener<Message> mMessageListener;
|
private Listener<IMessage> mMessageListener;
|
||||||
private boolean mAssemblingMessage = false;
|
private boolean mAssemblingMessage = false;
|
||||||
private CorrectedBinaryMessage mBinaryMessage;
|
private CorrectedBinaryMessage mBinaryMessage;
|
||||||
private P25P1DataUnitID mDataUnitID;
|
private P25P1DataUnitID mDataUnitID;
|
||||||
@@ -153,7 +152,7 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
|
|||||||
*
|
*
|
||||||
* @param messageListener to receive framed and decoded messages
|
* @param messageListener to receive framed and decoded messages
|
||||||
*/
|
*/
|
||||||
public void setListener(Listener<Message> messageListener)
|
public void setListener(Listener<IMessage> messageListener)
|
||||||
{
|
{
|
||||||
mMessageListener = messageListener;
|
mMessageListener = messageListener;
|
||||||
}
|
}
|
||||||
@@ -346,7 +345,7 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
P25Message message = P25MessageFactory.create(mDataUnitID, mNAC, getTimestamp(), mBinaryMessage);
|
P25P1Message message = P25MessageFactory.create(mDataUnitID, mNAC, getTimestamp(), mBinaryMessage);
|
||||||
mMessageListener.receive(message);
|
mMessageListener.receive(message);
|
||||||
reset(mDataUnitID.getMessageLength());
|
reset(mDataUnitID.getMessageLength());
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,44 +20,81 @@ package io.github.dsheirer.module.decode.p25.phase1;
|
|||||||
|
|
||||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.message.Message;
|
import io.github.dsheirer.module.decode.p25.P25FrequencyBandPreloadDataContent;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver;
|
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.ExtendedSourceLinkControlWord;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDU1Message;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
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.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class P25P1MessageProcessor implements Listener<Message>
|
/**
|
||||||
|
* APCO25 Phase 1 Message Processor.
|
||||||
|
*
|
||||||
|
* Performs post-message creation processing before the message is sent downstream.
|
||||||
|
*/
|
||||||
|
public class P25P1MessageProcessor implements Listener<IMessage>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(P25P1MessageProcessor.class);
|
private final static Logger mLog = LoggerFactory.getLogger(P25P1MessageProcessor.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downstream message listener
|
||||||
|
*/
|
||||||
private Listener<IMessage> mMessageListener;
|
private Listener<IMessage> mMessageListener;
|
||||||
|
|
||||||
/* Map of up to 16 band identifiers per RFSS. These identifier update
|
/**
|
||||||
* messages are inserted into any message that conveys channel information
|
* Map of up to 16 band identifiers per RFSS. These identifier update messages are inserted into any message that
|
||||||
* so that the uplink/downlink frequencies can be calculated */
|
* conveys channel information so that the uplink/downlink frequencies can be calculated
|
||||||
|
*/
|
||||||
private Map<Integer,IFrequencyBand> mFrequencyBandMap = new TreeMap<Integer,IFrequencyBand>();
|
private Map<Integer,IFrequencyBand> mFrequencyBandMap = new TreeMap<Integer,IFrequencyBand>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary holding of an extended source link control message while it awaits the extension message to arrive.
|
||||||
|
*/
|
||||||
|
private ExtendedSourceLinkControlWord mExtendedSourceLinkControlWord;
|
||||||
|
|
||||||
public P25P1MessageProcessor()
|
public P25P1MessageProcessor()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void receive(Message message)
|
* Preloads frequency band information from the control channel.
|
||||||
|
* @param content to preload
|
||||||
|
*/
|
||||||
|
public void preload(P25FrequencyBandPreloadDataContent content)
|
||||||
|
{
|
||||||
|
if(content.hasData())
|
||||||
|
{
|
||||||
|
for(IFrequencyBand frequencyBand: content.getData())
|
||||||
|
{
|
||||||
|
mFrequencyBandMap.put(frequencyBand.getIdentifier(), frequencyBand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(IMessage message)
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Capture frequency band identifier messages and inject them into any
|
|
||||||
* messages that require band information in order to calculate the
|
|
||||||
* up-link and down-link frequencies for any numeric channel references
|
|
||||||
* contained within the message.
|
|
||||||
*/
|
|
||||||
if(message.isValid())
|
if(message.isValid())
|
||||||
{
|
{
|
||||||
/* Insert frequency band identifier update messages into channel-type messages */
|
//Reassemble extended source link control messages.
|
||||||
|
if(message instanceof LDU1Message ldu)
|
||||||
|
{
|
||||||
|
reassembleLC(ldu.getLinkControlWord());
|
||||||
|
}
|
||||||
|
else if(message instanceof TDULinkControlMessage tdu)
|
||||||
|
{
|
||||||
|
reassembleLC(tdu.getLinkControlWord());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Insert frequency band identifier update messages into channel-type messages */
|
||||||
if(message instanceof IFrequencyBandReceiver)
|
if(message instanceof IFrequencyBandReceiver)
|
||||||
{
|
{
|
||||||
IFrequencyBandReceiver receiver = (IFrequencyBandReceiver)message;
|
IFrequencyBandReceiver receiver = (IFrequencyBandReceiver)message;
|
||||||
@@ -78,8 +115,7 @@ public class P25P1MessageProcessor implements Listener<Message>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Store band identifiers so that they can be injected into channel
|
//Store band identifiers so that they can be injected into channel type messages
|
||||||
* type messages */
|
|
||||||
if(message instanceof IFrequencyBand)
|
if(message instanceof IFrequencyBand)
|
||||||
{
|
{
|
||||||
IFrequencyBand bandIdentifier = (IFrequencyBand)message;
|
IFrequencyBand bandIdentifier = (IFrequencyBand)message;
|
||||||
@@ -93,6 +129,30 @@ public class P25P1MessageProcessor implements Listener<Message>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes link control words to reassemble source ID extension messages.
|
||||||
|
* @param linkControlWord to process
|
||||||
|
*/
|
||||||
|
private void reassembleLC(LinkControlWord linkControlWord)
|
||||||
|
{
|
||||||
|
if(linkControlWord instanceof ExtendedSourceLinkControlWord eslcw)
|
||||||
|
{
|
||||||
|
mExtendedSourceLinkControlWord = eslcw;
|
||||||
|
}
|
||||||
|
else if(linkControlWord instanceof LCSourceIDExtension sie && mExtendedSourceLinkControlWord != null)
|
||||||
|
{
|
||||||
|
mExtendedSourceLinkControlWord.setSourceIDExtension(sie);
|
||||||
|
mExtendedSourceLinkControlWord = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//The source extension message should always immediately follow the message that is being extended, so if
|
||||||
|
//we get a message that is not an extended message or the extension, then we've missed the extension and we
|
||||||
|
//should nullify any extended message that's waiting for the extension.
|
||||||
|
mExtendedSourceLinkControlWord = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
mFrequencyBandMap.clear();
|
mFrequencyBandMap.clear();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,16 +45,15 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SND
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SecondaryControlChannelBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SecondaryControlChannelBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SecondaryControlChannelBroadcastExplicit;
|
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SecondaryControlChannelBroadcastExplicit;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SystemServiceBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.SystemServiceBroadcast;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the network configuration details of a P25 Phase 1 network from the broadcast messages
|
* Tracks the network configuration details of a P25 Phase 1 network from the broadcast messages
|
||||||
@@ -236,7 +235,7 @@ public class P25P1NetworkConfigurationMonitor
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CHANNEL_IDENTIFIER_UPDATE:
|
case CHANNEL_IDENTIFIER_UPDATE:
|
||||||
case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT:
|
case CHANNEL_IDENTIFIER_UPDATE_VU:
|
||||||
if(lcw instanceof IFrequencyBand)
|
if(lcw instanceof IFrequencyBand)
|
||||||
{
|
{
|
||||||
IFrequencyBand band = (IFrequencyBand)lcw;
|
IFrequencyBand band = (IFrequencyBand)lcw;
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package io.github.dsheirer.module.decode.p25.phase1.message;
|
||||||
|
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to declare a frequency band
|
||||||
|
*/
|
||||||
|
public class P25FrequencyBand implements IFrequencyBand
|
||||||
|
{
|
||||||
|
private int mIdentifier;
|
||||||
|
private long mBaseFrequency;
|
||||||
|
private long mTransmitOffset;
|
||||||
|
private long mChannelSpacing;
|
||||||
|
private int mBandwidth;
|
||||||
|
private int mTimeslotCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an instance
|
||||||
|
* @param identifier for this frequency band
|
||||||
|
* @param baseFrequency in Hertz
|
||||||
|
* @param transmitOffset in Hertz
|
||||||
|
* @param channelSpacing in Hertz
|
||||||
|
* @param bandwidth in Hertz
|
||||||
|
* @param timeslotCount for number of timeslots, 1 or 2
|
||||||
|
*/
|
||||||
|
public P25FrequencyBand(int identifier, long baseFrequency, long transmitOffset, long channelSpacing,
|
||||||
|
int bandwidth, int timeslotCount)
|
||||||
|
{
|
||||||
|
mIdentifier = identifier;
|
||||||
|
mChannelSpacing = channelSpacing;
|
||||||
|
mBaseFrequency = baseFrequency;
|
||||||
|
mBandwidth = bandwidth;
|
||||||
|
mTransmitOffset = transmitOffset;
|
||||||
|
mTimeslotCount = timeslotCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIdentifier()
|
||||||
|
{
|
||||||
|
return mIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getChannelSpacing()
|
||||||
|
{
|
||||||
|
return mChannelSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBaseFrequency()
|
||||||
|
{
|
||||||
|
return mBaseFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBandwidth()
|
||||||
|
{
|
||||||
|
return mBandwidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTransmitOffset()
|
||||||
|
{
|
||||||
|
return mTransmitOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDownlinkFrequency(int channelNumber)
|
||||||
|
{
|
||||||
|
if(isTDMA())
|
||||||
|
{
|
||||||
|
return getBaseFrequency() + (getChannelSpacing() * (int)(FastMath.floor(channelNumber / getTimeslotCount())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBaseFrequency() + (getChannelSpacing() * channelNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getUplinkFrequency(int channelNumber)
|
||||||
|
{
|
||||||
|
return getDownlinkFrequency(channelNumber) + getTransmitOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTDMA()
|
||||||
|
{
|
||||||
|
return getTimeslotCount() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTimeslotCount()
|
||||||
|
{
|
||||||
|
return mTimeslotCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,7 +49,7 @@ public class P25MessageFactory
|
|||||||
* correction
|
* correction
|
||||||
* @return constructed message parser
|
* @return constructed message parser
|
||||||
*/
|
*/
|
||||||
public static P25Message create(P25P1DataUnitID dataUnitID, int nac, long timestamp, CorrectedBinaryMessage message)
|
public static P25P1Message create(P25P1DataUnitID dataUnitID, int nac, long timestamp, CorrectedBinaryMessage message)
|
||||||
{
|
{
|
||||||
switch(dataUnitID)
|
switch(dataUnitID)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,25 +20,32 @@ package io.github.dsheirer.module.decode.p25.phase1.message;
|
|||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.message.Message;
|
import io.github.dsheirer.message.IMessage;
|
||||||
|
import io.github.dsheirer.message.TimeslotMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.APCO25Nac;
|
import io.github.dsheirer.module.decode.p25.identifier.APCO25Nac;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
public abstract class P25Message extends Message
|
/**
|
||||||
|
* Base APCO25 Phase 1 Message
|
||||||
|
*/
|
||||||
|
public abstract class P25P1Message extends TimeslotMessage implements IMessage
|
||||||
{
|
{
|
||||||
public enum DuplexMode
|
//P25 Phase 1 ICD defines octets starting at zero.
|
||||||
{
|
public static final int OCTET_0_BIT_0 = 0;
|
||||||
HALF, FULL
|
public static final int OCTET_1_BIT_8 = 8;
|
||||||
}
|
public static final int OCTET_2_BIT_16 = 16;
|
||||||
|
public static final int OCTET_3_BIT_24 = 24;
|
||||||
|
public static final int OCTET_4_BIT_32 = 32;
|
||||||
|
public static final int OCTET_5_BIT_40 = 40;
|
||||||
|
public static final int OCTET_6_BIT_48 = 48;
|
||||||
|
public static final int OCTET_7_BIT_56 = 56;
|
||||||
|
public static final int OCTET_8_BIT_64 = 64;
|
||||||
|
public static final int OCTET_9_BIT_72 = 72;
|
||||||
|
public static final int OCTET_10_BIT_80 = 80;
|
||||||
|
public static final int OCTET_11_BIT_88 = 88;
|
||||||
|
public static final int OCTET_12_BIT_96 = 96;
|
||||||
|
|
||||||
public enum SessionMode
|
|
||||||
{
|
|
||||||
PACKET, CIRCUIT
|
|
||||||
}
|
|
||||||
|
|
||||||
private CorrectedBinaryMessage mMessage;
|
|
||||||
private boolean mValid = true;
|
|
||||||
private Identifier mNAC;
|
private Identifier mNAC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,10 +55,9 @@ public abstract class P25Message extends Message
|
|||||||
* @param nac Network Access Code (NAC) for the message
|
* @param nac Network Access Code (NAC) for the message
|
||||||
* @param timestamp when the message was transmitted
|
* @param timestamp when the message was transmitted
|
||||||
*/
|
*/
|
||||||
public P25Message(CorrectedBinaryMessage message, int nac, long timestamp)
|
public P25P1Message(CorrectedBinaryMessage message, int nac, long timestamp)
|
||||||
{
|
{
|
||||||
super(timestamp);
|
super(message, 0, timestamp);
|
||||||
mMessage = message;
|
|
||||||
mNAC = APCO25Nac.create(nac);
|
mNAC = APCO25Nac.create(nac);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +67,7 @@ public abstract class P25Message extends Message
|
|||||||
* @param message containing the binary message and optional corrected bit count
|
* @param message containing the binary message and optional corrected bit count
|
||||||
* @param nac Network Access Code (NAC) for the message
|
* @param nac Network Access Code (NAC) for the message
|
||||||
*/
|
*/
|
||||||
public P25Message(CorrectedBinaryMessage message, int nac)
|
public P25P1Message(CorrectedBinaryMessage message, int nac)
|
||||||
{
|
{
|
||||||
this(message, nac, System.currentTimeMillis());
|
this(message, nac, System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
@@ -71,28 +77,15 @@ public abstract class P25Message extends Message
|
|||||||
* @param nac Network Access Code (NAC) for the message
|
* @param nac Network Access Code (NAC) for the message
|
||||||
* @param timestamp when the message was transmitted
|
* @param timestamp when the message was transmitted
|
||||||
*/
|
*/
|
||||||
protected P25Message(int nac, long timestamp)
|
protected P25P1Message(int nac, long timestamp)
|
||||||
{
|
{
|
||||||
super(timestamp);
|
super(null, 0, timestamp);
|
||||||
mNAC = APCO25Nac.create(nac);
|
mNAC = APCO25Nac.create(nac);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes the message binary sequence.
|
* NAC code for this message
|
||||||
*/
|
*/
|
||||||
protected void setMessage(CorrectedBinaryMessage message)
|
|
||||||
{
|
|
||||||
mMessage = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The transmitted binary message
|
|
||||||
*/
|
|
||||||
public CorrectedBinaryMessage getMessage()
|
|
||||||
{
|
|
||||||
return mMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Identifier getNAC()
|
public Identifier getNAC()
|
||||||
{
|
{
|
||||||
return mNAC;
|
return mNAC;
|
||||||
@@ -125,21 +118,4 @@ public abstract class P25Message extends Message
|
|||||||
{
|
{
|
||||||
return Protocol.APCO25;
|
return Protocol.APCO25;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if this message is valid and has passed all error detection and correction routines.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean isValid()
|
|
||||||
{
|
|
||||||
return mValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets this message to the valid/invalid state. The default state is valid (true).
|
|
||||||
*/
|
|
||||||
protected void setValid(boolean valid)
|
|
||||||
{
|
|
||||||
mValid = valid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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.module.decode.p25.phase1.message;
|
package io.github.dsheirer.module.decode.p25.phase1.message;
|
||||||
@@ -23,20 +22,19 @@ package io.github.dsheirer.module.decode.p25.phase1.message;
|
|||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P25 Message with unknown or unrecognized data unit id
|
* P25 Message with unknown or unrecognized data unit id
|
||||||
*/
|
*/
|
||||||
public class UnknownP25Message extends P25Message
|
public class UnknownP25Message extends P25P1Message
|
||||||
{
|
{
|
||||||
private P25P1DataUnitID mDataUnitID;
|
private P25P1DataUnitID mDataUnitID;
|
||||||
|
|
||||||
public UnknownP25Message(CorrectedBinaryMessage message, int nac, long timestamp, P25P1DataUnitID dataUnitID)
|
public UnknownP25Message(CorrectedBinaryMessage message, int nac, long timestamp, P25P1DataUnitID dataUnitID)
|
||||||
{
|
{
|
||||||
super(message, nac);
|
super(message, nac, timestamp);
|
||||||
mDataUnitID = dataUnitID;
|
mDataUnitID = dataUnitID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,7 +22,7 @@ package io.github.dsheirer.module.decode.p25.phase1.message.filter;
|
|||||||
import io.github.dsheirer.filter.Filter;
|
import io.github.dsheirer.filter.Filter;
|
||||||
import io.github.dsheirer.filter.FilterElement;
|
import io.github.dsheirer.filter.FilterElement;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +51,7 @@ public class P25OtherMessageFilter extends Filter<IMessage,String>
|
|||||||
@Override
|
@Override
|
||||||
public boolean canProcess(IMessage message)
|
public boolean canProcess(IMessage message)
|
||||||
{
|
{
|
||||||
return message instanceof P25Message && super.canProcess(message);
|
return message instanceof P25P1Message && super.canProcess(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,7 +62,7 @@ public class P25OtherMessageFilter extends Filter<IMessage,String>
|
|||||||
@Override
|
@Override
|
||||||
public String apply(IMessage message)
|
public String apply(IMessage message)
|
||||||
{
|
{
|
||||||
if(message instanceof P25Message)
|
if(message instanceof P25P1Message)
|
||||||
{
|
{
|
||||||
return OTHER_KEY;
|
return OTHER_KEY;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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.filter.FilterSet;
|
|||||||
import io.github.dsheirer.filter.SyncLossMessageFilter;
|
import io.github.dsheirer.filter.SyncLossMessageFilter;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.message.SyncLossMessage;
|
import io.github.dsheirer.message.SyncLossMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter set for P25 messages
|
* Filter set for P25 messages
|
||||||
@@ -53,6 +53,6 @@ public class P25P1MessageFilterSet extends FilterSet<IMessage>
|
|||||||
@Override
|
@Override
|
||||||
public boolean canProcess(IMessage message)
|
public boolean canProcess(IMessage message)
|
||||||
{
|
{
|
||||||
return message instanceof P25Message || message instanceof SyncLossMessage;
|
return message instanceof P25P1Message || message instanceof SyncLossMessage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 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.p25.phase1.message.hdu;
|
package io.github.dsheirer.module.decode.p25.phase1.message.hdu;
|
||||||
|
|
||||||
@@ -27,20 +24,19 @@ import io.github.dsheirer.edac.Golay18;
|
|||||||
import io.github.dsheirer.edac.ReedSolomon_63_47_17_P25;
|
import io.github.dsheirer.edac.ReedSolomon_63_47_17_P25;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message;
|
||||||
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
public class HDUMessage extends P25P1Message
|
||||||
|
|
||||||
public class HDUMessage extends P25Message
|
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(HDUMessage.class);
|
private final static Logger mLog = LoggerFactory.getLogger(HDUMessage.class);
|
||||||
|
|
||||||
private static final int[] GOLAY_WORD_STARTS = {0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198, 216, 234, 252,
|
private static final int[] GOLAY_WORD_STARTS = {0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198, 216, 234, 252,
|
||||||
270, 288, 306, 324, 342, 360, 278, 396, 414, 432, 450, 468, 486, 504, 522, 540, 558, 576, 594, 612, 630};
|
270, 288, 306, 324, 342, 360, 278, 396, 414, 432, 450, 468, 486, 504, 522, 540, 558, 576, 594, 612, 630};
|
||||||
|
|
||||||
private static final int[] CW_HEX_0 = {0, 1, 2, 3, 4, 5, 6};
|
private static final int[] CW_HEX_0 = {0, 1, 2, 3, 4, 5};
|
||||||
private static final int[] CW_HEX_1 = {18, 19, 20, 21, 22, 23};
|
private static final int[] CW_HEX_1 = {18, 19, 20, 21, 22, 23};
|
||||||
private static final int[] CW_HEX_2 = {36, 37, 38, 39, 40, 41};
|
private static final int[] CW_HEX_2 = {36, 37, 38, 39, 40, 41};
|
||||||
private static final int[] CW_HEX_3 = {54, 55, 56, 57, 58, 59};
|
private static final int[] CW_HEX_3 = {54, 55, 56, 57, 58, 59};
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.IntField;
|
||||||
|
import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25FullyQualifiedRadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended link control word. Base class for adding in the extension word for messages that require two link control
|
||||||
|
* fragments to fit the content.
|
||||||
|
*/
|
||||||
|
public abstract class ExtendedSourceLinkControlWord extends LinkControlWord
|
||||||
|
{
|
||||||
|
private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48);
|
||||||
|
private LCSourceIDExtension mSourceIDExtension;
|
||||||
|
private FullyQualifiedRadioIdentifier mSourceAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a Link Control Word from the binary message sequence.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
public ExtendedSourceLinkControlWord(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional source ID extension message for a fully qualified radio source.
|
||||||
|
*/
|
||||||
|
protected LCSourceIDExtension getSourceIDExtension()
|
||||||
|
{
|
||||||
|
return mSourceIDExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the extension message.
|
||||||
|
*/
|
||||||
|
public void setSourceIDExtension(LCSourceIDExtension extension)
|
||||||
|
{
|
||||||
|
mSourceIDExtension = extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message is carrying a source ID extension message.
|
||||||
|
*/
|
||||||
|
protected boolean hasSourceIDExtension()
|
||||||
|
{
|
||||||
|
return mSourceIDExtension != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source address
|
||||||
|
*/
|
||||||
|
public FullyQualifiedRadioIdentifier getSourceAddress()
|
||||||
|
{
|
||||||
|
if(mSourceAddress == null && hasSourceIDExtension())
|
||||||
|
{
|
||||||
|
int radio = getInt(SOURCE_ADDRESS);
|
||||||
|
int wacn = getSourceIDExtension().getWACN();
|
||||||
|
int system = getSourceIDExtension().getSystem();
|
||||||
|
int id = getSourceIDExtension().getId();
|
||||||
|
mSourceAddress = APCO25FullyQualifiedRadioIdentifier.createFrom(radio, wacn, system, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,8 +36,8 @@ public enum LinkControlOpcode
|
|||||||
TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER("TELEPHONE INTERCONNECT VOICE CHANNEL USER", 6),
|
TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER("TELEPHONE INTERCONNECT VOICE CHANNEL USER", 6),
|
||||||
TELEPHONE_INTERCONNECT_ANSWER_REQUEST("TELEPHONE INTERCONNECT ANSWER REQUEST", 7),
|
TELEPHONE_INTERCONNECT_ANSWER_REQUEST("TELEPHONE INTERCONNECT ANSWER REQUEST", 7),
|
||||||
RESERVED_08("RESERVED-08", 8),
|
RESERVED_08("RESERVED-08", 8),
|
||||||
RESERVED_09("RESERVED-09", 9),
|
SOURCE_ID_EXTENSION("SOURCE ID EXTENSION", 9),
|
||||||
RESERVED_0A("RESERVED-0A", 10),
|
UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED("UNIT-TO-UNIT VOICE CHANNEL USER EXTENDED", 10),
|
||||||
RESERVED_0B("RESERVED-0B", 11),
|
RESERVED_0B("RESERVED-0B", 11),
|
||||||
RESERVED_0C("RESERVED-0C", 12),
|
RESERVED_0C("RESERVED-0C", 12),
|
||||||
RESERVED_0D("RESERVED-0D", 13),
|
RESERVED_0D("RESERVED-0D", 13),
|
||||||
@@ -52,10 +52,10 @@ public enum LinkControlOpcode
|
|||||||
CALL_ALERT("CALL ALERT", 22),
|
CALL_ALERT("CALL ALERT", 22),
|
||||||
EXTENDED_FUNCTION_COMMAND("EXTENDED FUNCTION COMMAND", 23),
|
EXTENDED_FUNCTION_COMMAND("EXTENDED FUNCTION COMMAND", 23),
|
||||||
CHANNEL_IDENTIFIER_UPDATE("CHANNEL IDENTIFIER UPDATE", 24),
|
CHANNEL_IDENTIFIER_UPDATE("CHANNEL IDENTIFIER UPDATE", 24),
|
||||||
CHANNEL_IDENTIFIER_UPDATE_EXPLICIT("CHANNEL IDENTIFIER UPDATE EXPLICIT", 25),
|
CHANNEL_IDENTIFIER_UPDATE_VU("CHANNEL IDENTIFIER UPDATE EXPLICIT", 25),
|
||||||
RESERVED_1A("RESERVED-1A", 26),
|
STATUS_UPDATE_EXTENDED("STATUS UPDATE-SOURCE ID REQUIRED", 26),
|
||||||
RESERVED_1B("RESERVED-1B", 27),
|
MESSAGE_UPDATE_EXTENDED("MESSAGE UPDATE-SOURCE ID REQUIRED", 27),
|
||||||
RESERVED_1C("RESERVED-1C", 28),
|
EXTENDED_FUNCTION_COMMAND_EXTENDED("EXTENDED FUNCTION COMMAND-SOURCE ID REQUIRED", 28),
|
||||||
RESERVED_1D("RESERVED-1D", 29),
|
RESERVED_1D("RESERVED-1D", 29),
|
||||||
RESERVED_1E("RESERVED-1E", 30),
|
RESERVED_1E("RESERVED-1E", 30),
|
||||||
RESERVED_1F("RESERVED-1F", 31),
|
RESERVED_1F("RESERVED-1F", 31),
|
||||||
@@ -69,7 +69,7 @@ public enum LinkControlOpcode
|
|||||||
ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT("ADJACENT SITE STATUS EXPLICIT", 39),
|
ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT("ADJACENT SITE STATUS EXPLICIT", 39),
|
||||||
RFSS_STATUS_BROADCAST_EXPLICIT("RFSS STATUS BROADCAST EXPLICIT", 40),
|
RFSS_STATUS_BROADCAST_EXPLICIT("RFSS STATUS BROADCAST EXPLICIT", 40),
|
||||||
NETWORK_STATUS_BROADCAST_EXPLICIT("NETWORK STATUS BROADCAST EXPLICIT", 41),
|
NETWORK_STATUS_BROADCAST_EXPLICIT("NETWORK STATUS BROADCAST EXPLICIT", 41),
|
||||||
RESERVED_2A("RESERVED-2A", 42),
|
CONVENTIONAL_FALLBACK_INDICATION("CONVENTIONAL FALLBACK INDICATION", 42),
|
||||||
RESERVED_2B("RESERVED-2B", 43),
|
RESERVED_2B("RESERVED-2B", 43),
|
||||||
RESERVED_2C("RESERVED-2C", 44),
|
RESERVED_2C("RESERVED-2C", 44),
|
||||||
RESERVED_2D("RESERVED-2D", 45),
|
RESERVED_2D("RESERVED-2D", 45),
|
||||||
@@ -92,15 +92,18 @@ public enum LinkControlOpcode
|
|||||||
RESERVED_3E("RESERVED-3E", 62),
|
RESERVED_3E("RESERVED-3E", 62),
|
||||||
RESERVED_3F("RESERVED-3F", 63),
|
RESERVED_3F("RESERVED-3F", 63),
|
||||||
|
|
||||||
MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER("PATCH GROUP VOICE CHANNEL USER", 0),
|
MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER("MOTOROLA GROUP REGROUP VOICE CHANNEL USER", 0),
|
||||||
MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE("PATCH GROUP VOICE CHANNEL UPDATE", 1),
|
MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE("MOTOROLA GROUP REGROUP VOICE CHANNEL UPDATE", 1),
|
||||||
MOTOROLA_PATCH_GROUP_ADD("PATCH GROUP ADD", 3),
|
MOTOROLA_GROUP_REGROUP_ADD("MOTOROLA GROUP REGROUP ADD", 3),
|
||||||
MOTOROLA_PATCH_GROUP_DELETE("PATCH GROUP DELETE", 4),
|
MOTOROLA_GROUP_REGROUP_DELETE("MOTOROLA GROUP REGROUP DELETE", 4),
|
||||||
MOTOROLA_UNIT_GPS("UNIT GPS", 6),
|
MOTOROLA_UNIT_GPS("MOTOROLA UNIT GPS", 6),
|
||||||
MOTOROLA_TALK_COMPLETE("TALK_COMPLETE", 15),
|
MOTOROLA_EMERGENCY_ALARM_ACTIVATION("MOTOROLA EMERGENCY ALARM ACTIVATION", 10),
|
||||||
|
MOTOROLA_TALK_COMPLETE("MOTOROLA TALK_COMPLETE", 15),
|
||||||
|
MOTOROLA_RADIO_REPROGRAM_HEADER("MOTOROLA RADIO REPROGRAM HEADER", 21),
|
||||||
|
MOTOROLA_RADIO_REPROGRAM_RECORD("MOTOROLA RADIO REPROGRAM CONTINUATION", 23),
|
||||||
MOTOROLA_UNKNOWN("MOTOROLA UNKNOWN", -1),
|
MOTOROLA_UNKNOWN("MOTOROLA UNKNOWN", -1),
|
||||||
|
|
||||||
L3HARRIS_UNKNOWN_0A("UNKNOWN OPCODE 10", 10),
|
L3HARRIS_RETURN_TO_CONTROL_CHANNEL("UNKNOWN OPCODE 10", 10),
|
||||||
L3HARRIS_UNKNOWN_2A("UNKNOWN OPCODE 42", 42),
|
L3HARRIS_UNKNOWN_2A("UNKNOWN OPCODE 42", 42),
|
||||||
L3HARRIS_UNKNOWN_2B("UNKNOWN OPCODE 43", 43),
|
L3HARRIS_UNKNOWN_2B("UNKNOWN OPCODE 43", 43),
|
||||||
L3HARRIS_UNKNOWN("L3HARRIS UNKNOWN", -1),
|
L3HARRIS_UNKNOWN("L3HARRIS UNKNOWN", -1),
|
||||||
@@ -133,19 +136,19 @@ public enum LinkControlOpcode
|
|||||||
* Motorola Opcodes
|
* Motorola Opcodes
|
||||||
*/
|
*/
|
||||||
public static final EnumSet<LinkControlOpcode> MOTOROLA_OPCODES =
|
public static final EnumSet<LinkControlOpcode> MOTOROLA_OPCODES =
|
||||||
EnumSet.range(MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER, MOTOROLA_UNKNOWN);
|
EnumSet.range(MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER, MOTOROLA_UNKNOWN);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* L3Harris Opcodes
|
* L3Harris Opcodes
|
||||||
*/
|
*/
|
||||||
public static final EnumSet<LinkControlOpcode> L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_UNKNOWN_0A,
|
public static final EnumSet<LinkControlOpcode> L3HARRIS_OPCODES = EnumSet.of(L3HARRIS_RETURN_TO_CONTROL_CHANNEL,
|
||||||
L3HARRIS_UNKNOWN_2A, L3HARRIS_UNKNOWN_2B, L3HARRIS_UNKNOWN);
|
L3HARRIS_UNKNOWN_2A, L3HARRIS_UNKNOWN_2B, L3HARRIS_UNKNOWN);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network/channel related opcodes
|
* Network/channel related opcodes
|
||||||
*/
|
*/
|
||||||
public static final EnumSet<LinkControlOpcode> NETWORK_OPCODES = EnumSet.of(CHANNEL_IDENTIFIER_UPDATE,
|
public static final EnumSet<LinkControlOpcode> NETWORK_OPCODES = EnumSet.of(CHANNEL_IDENTIFIER_UPDATE,
|
||||||
CHANNEL_IDENTIFIER_UPDATE_EXPLICIT, SYSTEM_SERVICE_BROADCAST, SECONDARY_CONTROL_CHANNEL_BROADCAST,
|
CHANNEL_IDENTIFIER_UPDATE_VU, SYSTEM_SERVICE_BROADCAST, SECONDARY_CONTROL_CHANNEL_BROADCAST,
|
||||||
SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, ADJACENT_SITE_STATUS_BROADCAST,
|
SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT, ADJACENT_SITE_STATUS_BROADCAST,
|
||||||
ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT, RFSS_STATUS_BROADCAST, RFSS_STATUS_BROADCAST_EXPLICIT,
|
ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT, RFSS_STATUS_BROADCAST, RFSS_STATUS_BROADCAST_EXPLICIT,
|
||||||
NETWORK_STATUS_BROADCAST, NETWORK_STATUS_BROADCAST_EXPLICIT, PROTECTION_PARAMETER_BROADCAST);
|
NETWORK_STATUS_BROADCAST, NETWORK_STATUS_BROADCAST_EXPLICIT, PROTECTION_PARAMETER_BROADCAST);
|
||||||
@@ -205,7 +208,7 @@ public enum LinkControlOpcode
|
|||||||
switch(value)
|
switch(value)
|
||||||
{
|
{
|
||||||
case 10:
|
case 10:
|
||||||
return L3HARRIS_UNKNOWN_0A;
|
return L3HARRIS_RETURN_TO_CONTROL_CHANNEL;
|
||||||
case 42:
|
case 42:
|
||||||
return L3HARRIS_UNKNOWN_2A;
|
return L3HARRIS_UNKNOWN_2A;
|
||||||
case 43:
|
case 43:
|
||||||
@@ -217,17 +220,23 @@ public enum LinkControlOpcode
|
|||||||
switch(value)
|
switch(value)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
return MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER;
|
return MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER;
|
||||||
case 1:
|
case 1:
|
||||||
return MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE;
|
return MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE;
|
||||||
case 3:
|
case 3:
|
||||||
return MOTOROLA_PATCH_GROUP_ADD;
|
return MOTOROLA_GROUP_REGROUP_ADD;
|
||||||
case 4:
|
case 4:
|
||||||
return MOTOROLA_PATCH_GROUP_DELETE;
|
return MOTOROLA_GROUP_REGROUP_DELETE;
|
||||||
case 6:
|
case 6:
|
||||||
return MOTOROLA_UNIT_GPS;
|
return MOTOROLA_UNIT_GPS;
|
||||||
|
case 10:
|
||||||
|
return MOTOROLA_EMERGENCY_ALARM_ACTIVATION;
|
||||||
case 15:
|
case 15:
|
||||||
return MOTOROLA_TALK_COMPLETE;
|
return MOTOROLA_TALK_COMPLETE;
|
||||||
|
case 21:
|
||||||
|
return MOTOROLA_RADIO_REPROGRAM_HEADER;
|
||||||
|
case 23:
|
||||||
|
return MOTOROLA_RADIO_REPROGRAM_RECORD;
|
||||||
default:
|
default:
|
||||||
return MOTOROLA_UNKNOWN;
|
return MOTOROLA_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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,66 +14,73 @@
|
|||||||
*
|
*
|
||||||
* 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.p25.phase1.message.lc;
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.FragmentedIntField;
|
||||||
|
import io.github.dsheirer.bits.IntField;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.message.AbstractMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APCO 25 Link Control Word. This message word is contained in Logical Link Data Unit 1 and Terminator with
|
* APCO 25 Link Control Word. This message word is contained in Logical Link Data Unit 1 and Terminator with
|
||||||
* Link Control messages.
|
* Link Control messages.
|
||||||
*/
|
*/
|
||||||
public abstract class LinkControlWord
|
public abstract class LinkControlWord extends AbstractMessage
|
||||||
{
|
{
|
||||||
|
public static final int OCTET_0_BIT_0 = 0;
|
||||||
|
public static final int OCTET_1_BIT_8 = 8;
|
||||||
|
public static final int OCTET_2_BIT_16 = 16;
|
||||||
|
public static final int OCTET_3_BIT_24 = 24;
|
||||||
|
public static final int OCTET_4_BIT_32 = 32;
|
||||||
|
public static final int OCTET_5_BIT_40 = 40;
|
||||||
|
public static final int OCTET_6_BIT_48 = 48;
|
||||||
|
public static final int OCTET_7_BIT_56 = 56;
|
||||||
|
public static final int OCTET_8_BIT_64 = 64;
|
||||||
|
public static final int OCTET_9_BIT_72 = 72;
|
||||||
|
public static final int OCTET_10_BIT_80 = 80;
|
||||||
|
public static final int OCTET_11_BIT_88 = 88;
|
||||||
|
public static final int OCTET_12_BIT_96 = 96;
|
||||||
|
|
||||||
private static final int ENCRYPTION_FLAG = 0;
|
private static final int ENCRYPTION_FLAG = 0;
|
||||||
private static final int STANDARD_VENDOR_ID_FLAG = 1;
|
private static final int STANDARD_VENDOR_ID_FLAG = 1;
|
||||||
private static final int[] OPCODE = {2, 3, 4, 5, 6, 7};
|
private static final FragmentedIntField OPCODE = FragmentedIntField.of(2, 3, 4, 5, 6, 7);
|
||||||
private static final int[] VENDOR = {8, 9, 10, 11, 12, 13, 14, 15};
|
private static final IntField VENDOR = IntField.length8(OCTET_1_BIT_8);
|
||||||
|
|
||||||
private BinaryMessage mMessage;
|
|
||||||
private LinkControlOpcode mLinkControlOpcode;
|
private LinkControlOpcode mLinkControlOpcode;
|
||||||
private boolean mValid = true;
|
private boolean mValid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a Link Control Word from the binary message sequence.
|
* Constructs a Link Control Word from the binary message sequence.
|
||||||
*
|
*
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public LinkControlWord(BinaryMessage message)
|
public LinkControlWord(CorrectedBinaryMessage message)
|
||||||
{
|
{
|
||||||
mMessage = message;
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binary message sequence for this LCW
|
* Indicates if the message passes CRC checks.
|
||||||
*/
|
|
||||||
protected BinaryMessage getMessage()
|
|
||||||
{
|
|
||||||
return mMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the valid flag for this message to mark a flag as invalid (false) or the default value is true.
|
|
||||||
*/
|
|
||||||
public void setValid(boolean valid)
|
|
||||||
{
|
|
||||||
mValid = valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if this link control word is valid and has passed all error detection and correction routines.
|
|
||||||
*/
|
*/
|
||||||
public boolean isValid()
|
public boolean isValid()
|
||||||
{
|
{
|
||||||
return mValid;
|
return mValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the valid flag for this message.
|
||||||
|
*/
|
||||||
|
public void setValid(boolean valid)
|
||||||
|
{
|
||||||
|
mValid = valid;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this is an encrypted LCW
|
* Indicates if this is an encrypted LCW
|
||||||
*/
|
*/
|
||||||
@@ -110,7 +116,7 @@ public abstract class LinkControlWord
|
|||||||
/**
|
/**
|
||||||
* Lookup the Vendor format for the specified LCW
|
* Lookup the Vendor format for the specified LCW
|
||||||
*/
|
*/
|
||||||
public static Vendor getVendor(BinaryMessage binaryMessage)
|
public static Vendor getVendor(CorrectedBinaryMessage binaryMessage)
|
||||||
{
|
{
|
||||||
if(isStandardVendorFormat(binaryMessage))
|
if(isStandardVendorFormat(binaryMessage))
|
||||||
{
|
{
|
||||||
@@ -140,13 +146,13 @@ public abstract class LinkControlWord
|
|||||||
*/
|
*/
|
||||||
public int getOpcodeNumber()
|
public int getOpcodeNumber()
|
||||||
{
|
{
|
||||||
return getMessage().getInt(OPCODE);
|
return getInt(OPCODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the link control word opcode from the binary message.
|
* Identifies the link control word opcode from the binary message.
|
||||||
*/
|
*/
|
||||||
public static LinkControlOpcode getOpcode(BinaryMessage binaryMessage)
|
public static LinkControlOpcode getOpcode(CorrectedBinaryMessage binaryMessage)
|
||||||
{
|
{
|
||||||
return LinkControlOpcode.fromValue(binaryMessage.getInt(OPCODE), getVendor(binaryMessage));
|
return LinkControlOpcode.fromValue(binaryMessage.getInt(OPCODE), getVendor(binaryMessage));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,14 +19,17 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.phase1.message.lc;
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode10;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisReturnToControlChannel;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode42;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode42;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode43;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris.LCHarrisUnknownOpcode43;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupAdd;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaEmergencyAlarmActivation;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupDelete;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupGroupDelete;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupAdd;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUpdate;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaGroupRegroupVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaRadioReprogramHeader;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaRadioReprogramRecord;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkComplete;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaTalkComplete;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnitGPS;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnitGPS;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnknownOpcode;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaUnknownOpcode;
|
||||||
@@ -34,14 +37,17 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacen
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacentSiteStatusBroadcastExplicit;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacentSiteStatusBroadcastExplicit;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCCallAlert;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCCallAlert;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCCallTermination;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCCallTermination;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCChannelIdentifierUpdate;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCChannelIdentifierUpdateVU;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCConventionalFallback;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCExtendedFunctionCommand;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCExtendedFunctionCommand;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCFrequencyBandUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCExtendedFunctionCommandExtended;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCFrequencyBandUpdateExplicit;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupAffiliationQuery;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupAffiliationQuery;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdate;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdateExplicit;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUpdateExplicit;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCMessageUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCMessageUpdate;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCMessageUpdateExtended;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCNetworkStatusBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCNetworkStatusBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCNetworkStatusBroadcastExplicit;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCNetworkStatusBroadcastExplicit;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCProtectionParameterBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCProtectionParameterBroadcast;
|
||||||
@@ -49,8 +55,10 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCRFSSSta
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCRFSSStatusBroadcastExplicit;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCRFSSStatusBroadcastExplicit;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSecondaryControlChannelBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSecondaryControlChannelBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSecondaryControlChannelBroadcastExplicit;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSecondaryControlChannelBroadcastExplicit;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSourceIDExtension;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusQuery;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusQuery;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusUpdate;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusUpdate;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCStatusUpdateExtended;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSystemServiceBroadcast;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCSystemServiceBroadcast;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectAnswerRequest;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectAnswerRequest;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectVoiceChannelUser;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCTelephoneInterconnectVoiceChannelUser;
|
||||||
@@ -58,6 +66,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitAut
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitRegistrationCommand;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitRegistrationCommand;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitToUnitAnswerRequest;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitToUnitAnswerRequest;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitToUnitVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCUnitToUnitVoiceChannelUserExtended;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory class for creating link control word (LCW) message parsers.
|
* Factory class for creating link control word (LCW) message parsers.
|
||||||
@@ -67,96 +76,114 @@ public class LinkControlWordFactory
|
|||||||
/**
|
/**
|
||||||
* Creates a link control word from the binary message sequence.
|
* Creates a link control word from the binary message sequence.
|
||||||
*
|
*
|
||||||
* @param binaryMessage containing the LCW binary message sequence.
|
* @param message containing the LCW binary message sequence.
|
||||||
*/
|
*/
|
||||||
public static LinkControlWord create(BinaryMessage binaryMessage)
|
public static LinkControlWord create(CorrectedBinaryMessage message)
|
||||||
{
|
{
|
||||||
LinkControlOpcode opcode = LinkControlWord.getOpcode(binaryMessage);
|
LinkControlOpcode opcode = LinkControlWord.getOpcode(message);
|
||||||
switch(opcode)
|
switch(opcode)
|
||||||
{
|
{
|
||||||
case ADJACENT_SITE_STATUS_BROADCAST:
|
case ADJACENT_SITE_STATUS_BROADCAST:
|
||||||
return new LCAdjacentSiteStatusBroadcast(binaryMessage);
|
return new LCAdjacentSiteStatusBroadcast(message);
|
||||||
case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT:
|
case ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT:
|
||||||
return new LCAdjacentSiteStatusBroadcastExplicit(binaryMessage);
|
return new LCAdjacentSiteStatusBroadcastExplicit(message);
|
||||||
case CALL_ALERT:
|
case CALL_ALERT:
|
||||||
return new LCCallAlert(binaryMessage);
|
return new LCCallAlert(message);
|
||||||
case CALL_TERMINATION_OR_CANCELLATION:
|
case CALL_TERMINATION_OR_CANCELLATION:
|
||||||
return new LCCallTermination(binaryMessage);
|
return new LCCallTermination(message);
|
||||||
case CHANNEL_IDENTIFIER_UPDATE:
|
case CHANNEL_IDENTIFIER_UPDATE:
|
||||||
return new LCFrequencyBandUpdate(binaryMessage);
|
return new LCChannelIdentifierUpdate(message);
|
||||||
case CHANNEL_IDENTIFIER_UPDATE_EXPLICIT:
|
case CHANNEL_IDENTIFIER_UPDATE_VU:
|
||||||
return new LCFrequencyBandUpdateExplicit(binaryMessage);
|
return new LCChannelIdentifierUpdateVU(message);
|
||||||
|
case CONVENTIONAL_FALLBACK_INDICATION:
|
||||||
|
return new LCConventionalFallback(message);
|
||||||
case EXTENDED_FUNCTION_COMMAND:
|
case EXTENDED_FUNCTION_COMMAND:
|
||||||
return new LCExtendedFunctionCommand(binaryMessage);
|
return new LCExtendedFunctionCommand(message);
|
||||||
|
case EXTENDED_FUNCTION_COMMAND_EXTENDED:
|
||||||
|
return new LCExtendedFunctionCommandExtended(message);
|
||||||
case GROUP_AFFILIATION_QUERY:
|
case GROUP_AFFILIATION_QUERY:
|
||||||
return new LCGroupAffiliationQuery(binaryMessage);
|
return new LCGroupAffiliationQuery(message);
|
||||||
case GROUP_VOICE_CHANNEL_USER:
|
case GROUP_VOICE_CHANNEL_USER:
|
||||||
return new LCGroupVoiceChannelUser(binaryMessage);
|
return new LCGroupVoiceChannelUser(message);
|
||||||
case GROUP_VOICE_CHANNEL_UPDATE:
|
case GROUP_VOICE_CHANNEL_UPDATE:
|
||||||
return new LCGroupVoiceChannelUpdate(binaryMessage);
|
return new LCGroupVoiceChannelUpdate(message);
|
||||||
case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT:
|
case GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT:
|
||||||
return new LCGroupVoiceChannelUpdateExplicit(binaryMessage);
|
return new LCGroupVoiceChannelUpdateExplicit(message);
|
||||||
case MESSAGE_UPDATE:
|
case MESSAGE_UPDATE:
|
||||||
return new LCMessageUpdate(binaryMessage);
|
return new LCMessageUpdate(message);
|
||||||
|
case MESSAGE_UPDATE_EXTENDED:
|
||||||
|
return new LCMessageUpdateExtended(message);
|
||||||
case NETWORK_STATUS_BROADCAST:
|
case NETWORK_STATUS_BROADCAST:
|
||||||
return new LCNetworkStatusBroadcast(binaryMessage);
|
return new LCNetworkStatusBroadcast(message);
|
||||||
case NETWORK_STATUS_BROADCAST_EXPLICIT:
|
case NETWORK_STATUS_BROADCAST_EXPLICIT:
|
||||||
return new LCNetworkStatusBroadcastExplicit(binaryMessage);
|
return new LCNetworkStatusBroadcastExplicit(message);
|
||||||
case PROTECTION_PARAMETER_BROADCAST:
|
case PROTECTION_PARAMETER_BROADCAST:
|
||||||
return new LCProtectionParameterBroadcast(binaryMessage);
|
return new LCProtectionParameterBroadcast(message);
|
||||||
case RFSS_STATUS_BROADCAST:
|
case RFSS_STATUS_BROADCAST:
|
||||||
return new LCRFSSStatusBroadcast(binaryMessage);
|
return new LCRFSSStatusBroadcast(message);
|
||||||
case RFSS_STATUS_BROADCAST_EXPLICIT:
|
case RFSS_STATUS_BROADCAST_EXPLICIT:
|
||||||
return new LCRFSSStatusBroadcastExplicit(binaryMessage);
|
return new LCRFSSStatusBroadcastExplicit(message);
|
||||||
case SECONDARY_CONTROL_CHANNEL_BROADCAST:
|
case SECONDARY_CONTROL_CHANNEL_BROADCAST:
|
||||||
return new LCSecondaryControlChannelBroadcast(binaryMessage);
|
return new LCSecondaryControlChannelBroadcast(message);
|
||||||
case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT:
|
case SECONDARY_CONTROL_CHANNEL_BROADCAST_EXPLICIT:
|
||||||
return new LCSecondaryControlChannelBroadcastExplicit(binaryMessage);
|
return new LCSecondaryControlChannelBroadcastExplicit(message);
|
||||||
|
case SOURCE_ID_EXTENSION:
|
||||||
|
return new LCSourceIDExtension(message);
|
||||||
case STATUS_QUERY:
|
case STATUS_QUERY:
|
||||||
return new LCStatusQuery(binaryMessage);
|
return new LCStatusQuery(message);
|
||||||
case STATUS_UPDATE:
|
case STATUS_UPDATE:
|
||||||
return new LCStatusUpdate(binaryMessage);
|
return new LCStatusUpdate(message);
|
||||||
|
case STATUS_UPDATE_EXTENDED:
|
||||||
|
return new LCStatusUpdateExtended(message);
|
||||||
case SYSTEM_SERVICE_BROADCAST:
|
case SYSTEM_SERVICE_BROADCAST:
|
||||||
return new LCSystemServiceBroadcast(binaryMessage);
|
return new LCSystemServiceBroadcast(message);
|
||||||
case TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
|
case TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
|
||||||
return new LCTelephoneInterconnectAnswerRequest(binaryMessage);
|
return new LCTelephoneInterconnectAnswerRequest(message);
|
||||||
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
|
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
|
||||||
return new LCTelephoneInterconnectVoiceChannelUser(binaryMessage);
|
return new LCTelephoneInterconnectVoiceChannelUser(message);
|
||||||
case UNIT_AUTHENTICATION_COMMAND:
|
case UNIT_AUTHENTICATION_COMMAND:
|
||||||
return new LCUnitAuthenticationCommand(binaryMessage);
|
return new LCUnitAuthenticationCommand(message);
|
||||||
case UNIT_REGISTRATION_COMMAND:
|
case UNIT_REGISTRATION_COMMAND:
|
||||||
return new LCUnitRegistrationCommand(binaryMessage);
|
return new LCUnitRegistrationCommand(message);
|
||||||
case UNIT_TO_UNIT_ANSWER_REQUEST:
|
case UNIT_TO_UNIT_ANSWER_REQUEST:
|
||||||
return new LCUnitToUnitAnswerRequest(binaryMessage);
|
return new LCUnitToUnitAnswerRequest(message);
|
||||||
case UNIT_TO_UNIT_VOICE_CHANNEL_USER:
|
case UNIT_TO_UNIT_VOICE_CHANNEL_USER:
|
||||||
return new LCUnitToUnitVoiceChannelUser(binaryMessage);
|
return new LCUnitToUnitVoiceChannelUser(message);
|
||||||
|
case UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED:
|
||||||
|
return new LCUnitToUnitVoiceChannelUserExtended(message);
|
||||||
|
|
||||||
case L3HARRIS_UNKNOWN_0A:
|
case L3HARRIS_RETURN_TO_CONTROL_CHANNEL:
|
||||||
return new LCHarrisUnknownOpcode10(binaryMessage);
|
return new LCHarrisReturnToControlChannel(message);
|
||||||
case L3HARRIS_UNKNOWN_2A:
|
case L3HARRIS_UNKNOWN_2A:
|
||||||
return new LCHarrisUnknownOpcode42(binaryMessage);
|
return new LCHarrisUnknownOpcode42(message);
|
||||||
case L3HARRIS_UNKNOWN_2B:
|
case L3HARRIS_UNKNOWN_2B:
|
||||||
return new LCHarrisUnknownOpcode43(binaryMessage);
|
return new LCHarrisUnknownOpcode43(message);
|
||||||
case L3HARRIS_UNKNOWN:
|
case L3HARRIS_UNKNOWN:
|
||||||
return new UnknownLinkControlWord(binaryMessage);
|
return new UnknownLinkControlWord(message);
|
||||||
|
|
||||||
case MOTOROLA_PATCH_GROUP_ADD:
|
case MOTOROLA_GROUP_REGROUP_ADD:
|
||||||
return new LCMotorolaPatchGroupAdd(binaryMessage);
|
return new LCMotorolaGroupRegroupAdd(message);
|
||||||
case MOTOROLA_PATCH_GROUP_DELETE:
|
case MOTOROLA_GROUP_REGROUP_DELETE:
|
||||||
return new LCMotorolaPatchGroupDelete(binaryMessage);
|
return new LCMotorolaGroupGroupDelete(message);
|
||||||
case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_USER:
|
case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_USER:
|
||||||
return new LCMotorolaPatchGroupVoiceChannelUser(binaryMessage);
|
return new LCMotorolaGroupRegroupVoiceChannelUser(message);
|
||||||
case MOTOROLA_TALK_COMPLETE:
|
case MOTOROLA_TALK_COMPLETE:
|
||||||
return new LCMotorolaTalkComplete(binaryMessage);
|
return new LCMotorolaTalkComplete(message);
|
||||||
case MOTOROLA_PATCH_GROUP_VOICE_CHANNEL_UPDATE:
|
case MOTOROLA_GROUP_REGROUP_VOICE_CHANNEL_UPDATE:
|
||||||
return new LCMotorolaPatchGroupVoiceChannelUpdate(binaryMessage);
|
return new LCMotorolaGroupRegroupVoiceChannelUpdate(message);
|
||||||
case MOTOROLA_UNIT_GPS:
|
case MOTOROLA_UNIT_GPS:
|
||||||
return new LCMotorolaUnitGPS(binaryMessage);
|
return new LCMotorolaUnitGPS(message);
|
||||||
|
case MOTOROLA_RADIO_REPROGRAM_HEADER:
|
||||||
|
return new LCMotorolaRadioReprogramHeader(message);
|
||||||
|
case MOTOROLA_RADIO_REPROGRAM_RECORD:
|
||||||
|
return new LCMotorolaRadioReprogramRecord(message);
|
||||||
|
case MOTOROLA_EMERGENCY_ALARM_ACTIVATION:
|
||||||
|
return new LCMotorolaEmergencyAlarmActivation(message);
|
||||||
case MOTOROLA_UNKNOWN:
|
case MOTOROLA_UNKNOWN:
|
||||||
return new LCMotorolaUnknownOpcode(binaryMessage);
|
return new LCMotorolaUnknownOpcode(message);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return new UnknownLinkControlWord(binaryMessage);
|
return new UnknownLinkControlWord(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2024 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,14 +14,13 @@
|
|||||||
*
|
*
|
||||||
* 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.p25.phase1.message.lc;
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ public class UnknownLinkControlWord extends LinkControlWord
|
|||||||
*
|
*
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public UnknownLinkControlWord(BinaryMessage message)
|
public UnknownLinkControlWord(CorrectedBinaryMessage message)
|
||||||
{
|
{
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.IntField;
|
||||||
|
import io.github.dsheirer.module.decode.p25.IServiceOptionsProvider;
|
||||||
|
import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base voice call link control word
|
||||||
|
*/
|
||||||
|
public abstract class VoiceLinkControlMessage extends LinkControlWord implements IServiceOptionsProvider
|
||||||
|
{
|
||||||
|
private static final IntField SERVICE_OPTIONS = IntField.length8(OCTET_2_BIT_16);
|
||||||
|
private VoiceServiceOptions mVoiceServiceOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a Link Control Word from the binary message sequence.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
public VoiceLinkControlMessage(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Options for this channel
|
||||||
|
*/
|
||||||
|
public VoiceServiceOptions getServiceOptions()
|
||||||
|
{
|
||||||
|
if(mVoiceServiceOptions == null)
|
||||||
|
{
|
||||||
|
mVoiceServiceOptions = new VoiceServiceOptions(getInt(SERVICE_OPTIONS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mVoiceServiceOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,8 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.IntField;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
@@ -28,20 +29,30 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* L3Harris Unknown Opcode 10 (0x0A)
|
* L3Harris Opcode 10 (0x0A) - This seems to be a 'return to control channel' or 'private data call paging'.
|
||||||
* <p>
|
* <p>
|
||||||
* Observed in a traffic channel carried by a TDU during an SNDCP packet data session. The controller send an 'All
|
* Observed on a phase 1 traffic data channel carried by a TDU during an SNDCP packet data session. The controller sent
|
||||||
* blocks received' PDU to the radio in the middle of a stream of these TDU/LC messages and the traffic channel ended
|
* an 'All blocks received' PDU to the radio and an 'Activate TDS context' in the middle of a stream of these TDU/LC
|
||||||
* with Call Termination TDULC messages. So, this may be some form of current data session user information update.
|
* messages, and then the radio terminated the traffic channel. So, this may be some form of return to control
|
||||||
|
* channel or maybe a private data call paging where the mobile returns to the control channel to receive the private
|
||||||
|
* data channel grant for another data call
|
||||||
|
*
|
||||||
|
* When the radio returned to the control channel, control sent the following two MAC messages on the Phase 2 control:
|
||||||
|
* LOCCH-U NAC:9/x009 SIGNAL CUSTOM/UNKNOWN VENDOR:HARRIS ID:A4 OPCODE:160 LENGTH:9 MSG:A0A409AC0312014871 (radio 0x014871 go to data channel 0x0312??)
|
||||||
|
* LOCCH-U NAC:9/x009 SIGNAL CUSTOM/UNKNOWN VENDOR:HARRIS ID:A4 OPCODE:172 LENGTH:12 MSG:ACA40C000312014871980418 (from 0x014871 to 0x980418 unit-2-unit data channel grant?)
|
||||||
|
*
|
||||||
|
* Both messages seem to refer to channel 0-786 (0x0312) so this may be a unit-2-unit private Phase 1 call or maybe
|
||||||
|
* a private data call. Radio addresses: 0x014871 and 0x980418
|
||||||
|
*
|
||||||
|
* In a second observation (Rockwall, TX), the control channel used the SNDCP data channel grant to send the
|
||||||
|
* mobile to the data channel and then the data channel transmitted a sequence of TDULCs containing only this message.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class LCHarrisUnknownOpcode10 extends LinkControlWord
|
public class LCHarrisReturnToControlChannel extends LinkControlWord
|
||||||
{
|
{
|
||||||
private static final int[] UNKNOWN_1 = {16, 17, 18, 19, 20, 21, 22, 23};
|
private static final IntField UNKNOWN_1 = IntField.length8(OCTET_2_BIT_16);
|
||||||
private static final int[] TARGET_RADIO = {24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
|
private static final IntField SOURCE_RADIO = IntField.length24(OCTET_3_BIT_24);
|
||||||
42, 43, 44, 45, 46, 47};
|
private static final IntField TARGET_RADIO = IntField.length24(OCTET_6_BIT_48);
|
||||||
private static final int[] SOURCE_RADIO = {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 RadioIdentifier mSourceRadio;
|
private RadioIdentifier mSourceRadio;
|
||||||
private RadioIdentifier mTargetRadio;
|
private RadioIdentifier mTargetRadio;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
@@ -51,7 +62,7 @@ public class LCHarrisUnknownOpcode10 extends LinkControlWord
|
|||||||
*
|
*
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public LCHarrisUnknownOpcode10(BinaryMessage message)
|
public LCHarrisReturnToControlChannel(CorrectedBinaryMessage message)
|
||||||
{
|
{
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
@@ -71,7 +82,7 @@ public class LCHarrisUnknownOpcode10 extends LinkControlWord
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.append("L3HARRIS UNKNOWN OPCODE 10 ");
|
sb.append("L3HARRIS RETURN TO CONTROL CHANNEL OR PRIVATE DATA CALL PAGING");
|
||||||
sb.append(" FM:").append(getSourceRadio());
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getTargetRadio());
|
sb.append(" TO:").append(getTargetRadio());
|
||||||
sb.append(" UNK:").append(getUnknown());
|
sb.append(" UNK:").append(getUnknown());
|
||||||
@@ -82,7 +93,7 @@ public class LCHarrisUnknownOpcode10 extends LinkControlWord
|
|||||||
|
|
||||||
public String getUnknown()
|
public String getUnknown()
|
||||||
{
|
{
|
||||||
return getMessage().getHex(UNKNOWN_1, 2);
|
return getMessage().getHex(UNKNOWN_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,7 +103,7 @@ public class LCHarrisUnknownOpcode10 extends LinkControlWord
|
|||||||
{
|
{
|
||||||
if(mSourceRadio == null)
|
if(mSourceRadio == null)
|
||||||
{
|
{
|
||||||
mSourceRadio = APCO25RadioIdentifier.createFrom(getMessage().getInt(SOURCE_RADIO));
|
mSourceRadio = APCO25RadioIdentifier.createFrom(getInt(SOURCE_RADIO));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mSourceRadio;
|
return mSourceRadio;
|
||||||
@@ -105,7 +116,7 @@ public class LCHarrisUnknownOpcode10 extends LinkControlWord
|
|||||||
{
|
{
|
||||||
if(mTargetRadio == null)
|
if(mTargetRadio == null)
|
||||||
{
|
{
|
||||||
mTargetRadio = APCO25RadioIdentifier.createTo(getMessage().getInt(TARGET_RADIO));
|
mTargetRadio = APCO25RadioIdentifier.createTo(getInt(TARGET_RADIO));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mTargetRadio;
|
return mTargetRadio;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,7 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -38,7 +38,7 @@ public class LCHarrisUnknownOpcode42 extends LinkControlWord
|
|||||||
*
|
*
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public LCHarrisUnknownOpcode42(BinaryMessage message)
|
public LCHarrisUnknownOpcode42(CorrectedBinaryMessage message)
|
||||||
{
|
{
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
* Copyright (C) 2014-2024 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,7 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc.l3harris;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -38,7 +38,7 @@ public class LCHarrisUnknownOpcode43 extends LinkControlWord
|
|||||||
*
|
*
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public LCHarrisUnknownOpcode43(BinaryMessage message)
|
public LCHarrisUnknownOpcode43(CorrectedBinaryMessage message)
|
||||||
{
|
{
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.IntField;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motorola Link Control Opcode 10 appears to be an Emergency Alarm Activation message.
|
||||||
|
*
|
||||||
|
* This was observed in the following sequence:
|
||||||
|
* 1. Radio registers on network.
|
||||||
|
* 2. Radio assigned group and affiliation group
|
||||||
|
* 3. Network acknowledges radio's emergency alarm request
|
||||||
|
* 4. Network sends this message, interspersed with the ack message (3)
|
||||||
|
* 5. Network grants emergency group voice channel to radio and the same talkgroup references in this message, which
|
||||||
|
* is also the assigned talkgroup during initial registration.
|
||||||
|
* 6. Network grants second non-emerg group voice channel to radio and a second (unknown 0x39) talkgroup that may be
|
||||||
|
* a supervisor talkgroup.
|
||||||
|
*
|
||||||
|
* This link control message was also transmitted on active traffic channels.
|
||||||
|
*
|
||||||
|
* Note: the same opcode is used on both on control channel TSBK and traffic channel LCW messaging.
|
||||||
|
*/
|
||||||
|
public class LCMotorolaEmergencyAlarmActivation extends LinkControlWord
|
||||||
|
{
|
||||||
|
private static final IntField GROUP_ADDRESS = IntField.length16(OCTET_2_BIT_16);
|
||||||
|
//There seems to be room for another group address here, octets 4/5??
|
||||||
|
private static final IntField SOURCE_ADDRESS = IntField.length24(OCTET_6_BIT_48);
|
||||||
|
private TalkgroupIdentifier mGroupAddress;
|
||||||
|
private RadioIdentifier mSourceAddress;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a Link Control Word from the binary message sequence.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
public LCMotorolaEmergencyAlarmActivation(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("**CRC-FAILED** ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isEncrypted())
|
||||||
|
{
|
||||||
|
sb.append(" ENCRYPTED");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append("MOTOROLA EMERGENCY ALARM ACTIVATION RADIO:").append(getSourceAddress());
|
||||||
|
sb.append(" TALKGROUP:").append(getGroupAddress());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source Address that activated the emergency alarm.
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceAddress()
|
||||||
|
{
|
||||||
|
if(mSourceAddress == null)
|
||||||
|
{
|
||||||
|
mSourceAddress = APCO25RadioIdentifier.createTo(getInt(SOURCE_ADDRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup to be activated for the emergency voice call
|
||||||
|
*/
|
||||||
|
public TalkgroupIdentifier getGroupAddress()
|
||||||
|
{
|
||||||
|
if(mGroupAddress == null)
|
||||||
|
{
|
||||||
|
mGroupAddress = APCO25Talkgroup.create(getInt(GROUP_ADDRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mGroupAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of identifiers contained in this message
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getGroupAddress());
|
||||||
|
mIdentifiers.add(getSourceAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user