#1556 DMR Capacity Plus parsing enhancements. (#1557)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2023-05-20 07:44:07 -04:00
committed by GitHub
parent cbab7bb0c9
commit 9de83843a3
9 changed files with 253 additions and 61 deletions

View File

@@ -64,6 +64,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasComp
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
@@ -1048,13 +1049,27 @@ public class DMRDecoderState extends TimeslotDecoderState
updateRestChannel(((CapacityPlusRestChannel)message).getRestChannel());
}
break;
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
if(message instanceof CapacityPlusGroupVoiceChannelUser)
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
if(message instanceof CapacityPlusEncryptedVoiceChannelUser cpgvcu)
{
CapacityPlusGroupVoiceChannelUser cpgvcu = (CapacityPlusGroupVoiceChannelUser)message;
//This is the current channel - what do we do with the voice channel number?
// updateRestChannel(cpgvcu.getVoiceChannel());
if(isTerminator)
{
getIdentifierCollection().remove(Role.FROM);
getIdentifierCollection().update(cpgvcu.getTalkgroup());
}
else
{
getIdentifierCollection().update(message.getIdentifiers());
ServiceOptions serviceOptions = cpgvcu.getServiceOptions();
updateCurrentCall(serviceOptions.isEncrypted() ? DecodeEventType.CALL_GROUP_ENCRYPTED :
DecodeEventType.CALL_GROUP, serviceOptions.toString(), message.getTimestamp());
}
}
break;
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
if(message instanceof CapacityPlusGroupVoiceChannelUser cpgvcu)
{
updateRestChannel(cpgvcu.getRestChannel());
if(isTerminator)
{

View File

@@ -396,7 +396,7 @@ public class DMRMessageFramer implements Listener<Dibit>, IDMRBurstDetectListene
// String file = path + "20200710_053632_9600BPS_DMR_Niles_Radio_Coconino_Control.bits";
//Cap+ Multi-Site 1 - Data Revert LSN1/2 with CSBKO 62 Site status
// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210133_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210133_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_210845_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
// String file = path + "Texas_Dallas_Aerowave_Capacity_Plus_Multi_Site/20200716_212309_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
@@ -404,7 +404,7 @@ public class DMRMessageFramer implements Listener<Dibit>, IDMRBurstDetectListene
// String file = path + "Texas_Hunt_Farmers_Electric_Capacity_Plus_Multi_Site/20200714_224018_9600BPS_DMR_Farmers_Electric_Cooperative_Hunt_LCN_3.bits"; //This may have PLL mis-align issues
//Cap+ Multi-Site - Dallas Unknown
String file = path + "Texas_Dallas_Unknown_1_Capacity_Plus_Multi_Site/20200716_211233_9600BPS_DMR_Dallas_Unk_Dallas_Unk.bits";
// String file = path + "Texas_Dallas_Unknown_1_Capacity_Plus_Multi_Site/20200716_211233_9600BPS_DMR_Dallas_Unk_Dallas_Unk.bits";
//Hytera Short Data = Proprietary (encrypted)
// String file = path + "20200716_222839_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits"; //Hytera short data packets

View File

@@ -72,7 +72,7 @@ public abstract class DataMessageWithLinkControl extends DataMessage
StringBuilder sb = new StringBuilder();
if(hasRAS())
{
sb.append(" RAS:").append(getBPTCReservedBits());
sb.append("RAS:").append(getBPTCReservedBits()).append(" ");
}
sb.append(getSlotType());
sb.append(" ").append(getLCMessage());

View File

@@ -44,9 +44,6 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
private static final int[] REST_LSN = new int[]{20, 21, 22, 23};
private static final int[] LSN_VOICE_BITMAP = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int LSN_1_8_BITMAP_START = 24;
private static final int[][] VOICE_TALKGROUPS = new int[][]{{32, 33, 34, 35, 36, 37, 38, 39},
{40, 41, 42, 43, 44, 45, 46, 47}, {48, 49, 50, 51, 52, 53, 54, 55}, {56, 57, 58, 59, 60, 61, 62, 63},
{64, 65, 66, 67, 68, 69, 70, 71}, {72, 73, 74, 75, 76, 77, 78, 79}};
private DMRLogicalChannel mRestChannel;
private List<Identifier> mIdentifiers;
@@ -117,8 +114,14 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
*/
private String getActivityFragments()
{
StringBuilder sb = new StringBuilder();
sb.append("VOICE LSN ");
if(getMessage().get(48) && getMessage().get(56))
{
int a = 0; //debug
}
int[] talkgroups = new int[17];
int[] radios = new int[17];
boolean hasIdentifier = false;
int pointer = LSN_1_8_BITMAP_START;
//Process voice LSNs 1-8
@@ -133,31 +136,29 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
if(getMessage().get(x))
{
hasIdentifier = true;
if(pointer <= 72)
{
sb.append(lsn).append(":").append(getMessage().getInt(BYTE, pointer)).append(" ");
talkgroups[lsn] = getMessage().getInt(BYTE, pointer);
pointer += 8;
}
else
{
sb.append(lsn).append(":A ");
talkgroups[lsn] = -1;
}
}
else
{
sb.append(lsn).append(":* ");
}
}
}
else
{
sb.append("1-8:* ");
pointer += 8;
}
//Process voice LSNs 9 - 16
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
{
hasIdentifier = true;
int highLsnBitmap = pointer;
pointer += 8;
@@ -170,30 +171,25 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
{
if(pointer <= 72)
{
sb.append(lsn).append(":").append(getMessage().getInt(BYTE, pointer)).append(" ");
talkgroups[lsn] = getMessage().getInt(BYTE, pointer);
pointer += 8;
}
else
{
sb.append(lsn).append(":A ");
talkgroups[lsn] = -1;
}
}
else
{
sb.append(lsn).append(":* ");
}
}
}
else
{
sb.append("9-16:* ");
pointer += 8;
}
//Process Data and Private Radio IDs - first bit in radio options byte is set to indicate more activity
//Process Radio IDs - first bit in radio options byte is set to indicate more activity
if(pointer <= 72 && getMessage().get(pointer))
{
sb.append("DATA LSN ");
hasIdentifier = true;
pointer += 8;
//If we have the data revert channel bitmap ...
@@ -211,23 +207,57 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
{
if(pointer <= 64)
{
sb.append(lsn).append(":").append(getMessage().getInt(TWO_BYTES, pointer)).append(" ");
radios[lsn] = getMessage().getInt(TWO_BYTES, pointer);
pointer += 16;
}
else
{
sb.append(lsn).append(":A ");
radios[lsn] = -1;
}
}
else
{
sb.append(lsn).append(":* ");
}
}
}
else
}
StringBuilder sb = new StringBuilder();
if(!hasIdentifier)
{
sb.append("IDLE LSN: 1-16");
}
else
{
sb.append("ACTIVE LSN");
for(int x = 1; x < 17; x++)
{
sb.append("1-8:- ");
if(talkgroups[x] != 0 || radios[x] != 0)
{
sb.append(" ").append(x);
if(talkgroups[x] == 0)
{
if(radios[x] < 0)
{
sb.append(":(R)A"); //Radio active on channel but identifier is in a continuation message
}
else if(radios[x] > 0)
{
sb.append(":(R)").append(radios[x]); //Active radio ID for channel
}
else
{
sb.append(":"); //No radio or talkgroup active on channel
}
}
else if(talkgroups[x] < 0)
{
sb.append(":(T)A"); //Talkgroup active on channel but identifier is in a continuation message
}
else
{
sb.append(":(T)").append(talkgroups[x]); //Active talkgroup ID for channel.
}
}
}
}

View File

@@ -36,6 +36,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMe
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ActivityUpdateMessage;
@@ -114,6 +115,9 @@ public class LCMessageFactory
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
flc = new CapacityPlusGroupVoiceChannelUser(message, timestamp, timeslot);
break;
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
break;
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
flc = new CapacityPlusWideAreaVoiceChannelUser(message, timestamp, timeslot);
break;

View File

@@ -46,6 +46,8 @@ public enum LCOpcode
FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"),
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
//Observed on Cap+ Multi-Site System during an encrypted voice call
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
//Cap+ opcodes from https://forums.radioreference.com/threads/understanding-capacity-plus-trunking-some-more.452566/
//FLCO 0: Group Call Maintenance
//FLCO 3: Private Call Maintenance (TermLC)

View File

@@ -0,0 +1,140 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus - Yet Another Voice Channel User Message - Why? Is this one specific to Encrypted calls?
*/
public class CapacityPlusEncryptedVoiceChannelUser extends CapacityPlusVoiceChannelUser
{
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] UNKNOWN_2 = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] UNKNOWN_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance.
*
* @param message for the link control payload
*/
public CapacityPlusEncryptedVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
if(isReservedBitSet())
{
sb.append(" RESERVED-BIT");
}
sb.append("FLC MOTOROLA CAP+ ENCRYPTED VOICE CHANNEL USER");
sb.append(" FM:").append(getRadio());
sb.append(" TO:").append(getTalkgroup());
sb.append(" ").append(getServiceOptions());
sb.append(" UNK1:").append(getUnknown1());
sb.append(" UNK2:").append(getUnknown2());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Unknown 8-bit fields
*/
public String getUnknown1()
{
return getMessage().getHex(UNKNOWN_1, 2);
}
public String getUnknown2()
{
return getMessage().getHex(UNKNOWN_2, 2);
}
public String getUnknown3()
{
return getMessage().getHex(UNKNOWN_3, 2);
}
/**
* Source radio address
*/
public RadioIdentifier getRadio()
{
if(mRadio == null)
{
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mRadio;
}
/**
* Talkgroup address
*/
public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
}
return mTalkgroup;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
}
return mIdentifiers;
}
}

View File

@@ -39,8 +39,8 @@ public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelU
private static final int[] CAPACITY_PLUS_GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] CONVENTIONAL_GROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] VOICE_CHANNEL_REPEATER = new int[]{51, 52, 53, 54};
private static final int[] VOICE_CHANNEL_TIMESLOT = new int[]{55};
private static final int[] REST_CHANNEL = new int[]{51, 52, 53, 54};
private static final int[] REST_CHANNEL_TIMESLOT = new int[]{55};
private static final int[] CAPACITY_PLUS_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] CONVENTIONAL_SOURCE_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
@@ -48,7 +48,7 @@ public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelU
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
private DMRLogicalChannel mVoiceChannel;
private DMRLogicalChannel mRestChannel;
private List<Identifier> mIdentifiers;
/**
@@ -92,10 +92,10 @@ public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelU
sb.append(" FM:").append(getRadio());
sb.append(" TO:").append(getTalkgroup());
if(hasVoiceChannel())
if(hasRestChannel())
{
sb.append(" ON CHANNEL:");
sb.append(getVoiceChannel());
sb.append(" REST:");
sb.append(getRestChannel());
}
sb.append(" ").append(getServiceOptions());
@@ -114,22 +114,22 @@ public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelU
/**
* Logical channel number (ie repeater number).
*/
public DMRLogicalChannel getVoiceChannel()
public DMRLogicalChannel getRestChannel()
{
if(mVoiceChannel == null)
if(mRestChannel == null)
{
mVoiceChannel = new DMRLogicalChannel(getVoiceChannelRepeater(), getVoiceChannelTimeslot());
mRestChannel = new DMRLogicalChannel(getRestChannelRepeater(), getRestChannelTimeslot());
}
return mVoiceChannel;
return mRestChannel;
}
/**
* Rest repeater number
*/
public int getVoiceChannelRepeater()
public int getRestChannelRepeater()
{
return getMessage().getInt(VOICE_CHANNEL_REPEATER) + 1;
return getMessage().getInt(REST_CHANNEL) + 1;
}
/**
@@ -137,17 +137,17 @@ public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelU
*
* @return
*/
public int getVoiceChannelTimeslot()
public int getRestChannelTimeslot()
{
return getMessage().getInt(VOICE_CHANNEL_TIMESLOT) + 1;
return getMessage().getInt(REST_CHANNEL_TIMESLOT) + 1;
}
/**
* Indicates if this message has a voice channel indicated for the call
* Indicates if this message has a rest channel indicated for the call
*/
public boolean hasVoiceChannel()
public boolean hasRestChannel()
{
return getServiceOptions().isCapacityPlus() && getVoiceChannelRepeater() != 0;
return getServiceOptions().isCapacityPlus() && getRestChannelRepeater() != 0;
}
/**
@@ -199,9 +199,9 @@ public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelU
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
if(hasVoiceChannel())
if(hasRestChannel())
{
mIdentifiers.add(getVoiceChannel());
mIdentifiers.add(getRestChannel());
}
}
@@ -214,7 +214,7 @@ public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelU
@Override
public int[] getLogicalTimeslotNumbers()
{
return getVoiceChannel().getLSNArray();
return getRestChannel().getLSNArray();
}
/**
@@ -227,9 +227,9 @@ public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelU
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(getVoiceChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
getVoiceChannel().setTimeslotFrequency(timeslotFrequency);
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
}
}