mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#1787 DMR Decoder enhancements for RAS enabled systems. Adds support for CSBKO:33 and FLCO:16 (#1794)
Co-authored-by: sheirerd <sheirerd@ainfosec.com>
This commit is contained in:
@@ -66,6 +66,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.CapacityMaxVoiceChannelUser;
|
||||
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.CapacityPlusWideAreaVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||
@@ -142,6 +143,16 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
|
||||
* @param message to check
|
||||
* @return true if ignore CRC checksums or if the message is valid, meaning the message has passed CRC check.
|
||||
*/
|
||||
private boolean isValid(IMessage message)
|
||||
{
|
||||
return mIgnoreCRCChecksums || message.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this decoder state has an (optional) traffic channel manager.
|
||||
*/
|
||||
@@ -206,21 +217,21 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
if(message.getTimeslot() == getTimeslot())
|
||||
{
|
||||
if(message instanceof VoiceMessage)
|
||||
if(message instanceof VoiceMessage voice)
|
||||
{
|
||||
processVoice((VoiceMessage)message);
|
||||
processVoice(voice);
|
||||
}
|
||||
else if(message instanceof DataMessage)
|
||||
else if(message instanceof DataMessage data)
|
||||
{
|
||||
processData((DataMessage)message);
|
||||
processData(data);
|
||||
}
|
||||
else if((message.isValid() || mIgnoreCRCChecksums) && message instanceof LCMessage)
|
||||
else if(isValid(message) && message instanceof LCMessage lcMessage)
|
||||
{
|
||||
processLinkControl((LCMessage)message, false);
|
||||
processLinkControl(lcMessage, false);
|
||||
}
|
||||
else if(message.isValid() && message instanceof DMRPacketMessage)
|
||||
else if(isValid(message) && message instanceof DMRPacketMessage packet)
|
||||
{
|
||||
processPacket((DMRPacketMessage)message);
|
||||
processPacket(packet);
|
||||
}
|
||||
else if(message instanceof UDTShortMessageService sms)
|
||||
{
|
||||
@@ -232,16 +243,15 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
//SLCO messages on timeslot 0 to catch capacity plus rest channel events
|
||||
else if((message.isValid() || mIgnoreCRCChecksums) && message.getTimeslot() == 0 && message instanceof LCMessage)
|
||||
else if(isValid(message) && message.getTimeslot() == 0 && message instanceof LCMessage lcMessage)
|
||||
{
|
||||
processLinkControl((LCMessage)message, false);
|
||||
processLinkControl(lcMessage, false);
|
||||
}
|
||||
|
||||
//Pass the message to the network configuration monitor, if this decoder state has a non-null instance
|
||||
if(mNetworkConfigurationMonitor != null && (message.isValid() || mIgnoreCRCChecksums) &&
|
||||
message instanceof DMRMessage)
|
||||
if(mNetworkConfigurationMonitor != null && isValid(message) && message instanceof DMRMessage dmrMessage)
|
||||
{
|
||||
mNetworkConfigurationMonitor.process((DMRMessage)message);
|
||||
mNetworkConfigurationMonitor.process(dmrMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +513,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
//Process the link control message to get the identifiers
|
||||
LCMessage lc = header.getLCMessage();
|
||||
|
||||
if(lc.isValid())
|
||||
if(isValid(lc))
|
||||
{
|
||||
processLinkControl(lc, false);
|
||||
}
|
||||
@@ -520,15 +530,15 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
switch(message.getSlotType().getDataType())
|
||||
{
|
||||
case CSBK:
|
||||
if((message.isValid() || mIgnoreCRCChecksums) && message instanceof CSBKMessage)
|
||||
if(isValid(message) && message instanceof CSBKMessage csbk)
|
||||
{
|
||||
processCSBK((CSBKMessage)message);
|
||||
processCSBK(csbk);
|
||||
}
|
||||
break;
|
||||
case VOICE_HEADER:
|
||||
if(message instanceof HeaderMessage)
|
||||
if(message instanceof HeaderMessage header)
|
||||
{
|
||||
processVoiceHeader((HeaderMessage)message);
|
||||
processVoiceHeader(header);
|
||||
}
|
||||
break;
|
||||
case USB_DATA:
|
||||
@@ -539,9 +549,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case MBC_ENC_HEADER:
|
||||
case DATA_ENC_HEADER:
|
||||
case CHANNEL_CONTROL_ENC_HEADER:
|
||||
if(message instanceof HeaderMessage)
|
||||
if(message instanceof HeaderMessage header)
|
||||
{
|
||||
processHeader((HeaderMessage)message);
|
||||
processHeader(header);
|
||||
}
|
||||
break;
|
||||
case SLOT_IDLE:
|
||||
@@ -579,7 +589,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
LCMessage lcMessage = terminator.getLCMessage();
|
||||
|
||||
if(lcMessage.isValid())
|
||||
if(isValid(lcMessage))
|
||||
{
|
||||
processLinkControl(lcMessage, true);
|
||||
}
|
||||
@@ -592,7 +602,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
LCMessage lcMessage = voiceHeader.getLCMessage();
|
||||
|
||||
if(lcMessage.isValid())
|
||||
if(isValid(lcMessage))
|
||||
{
|
||||
processLinkControl(lcMessage, false);
|
||||
}
|
||||
@@ -1133,6 +1143,23 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
||||
if(message instanceof CapacityMaxVoiceChannelUser cmvcu)
|
||||
{
|
||||
if(isTerminator)
|
||||
{
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cmvcu.getTalkgroup());
|
||||
}
|
||||
else
|
||||
{
|
||||
getIdentifierCollection().update(message.getIdentifiers());
|
||||
ServiceOptions serviceOptions = cmvcu.getServiceOptions();
|
||||
updateCurrentCall(serviceOptions.isEncrypted() ? DecodeEventType.CALL_GROUP_ENCRYPTED :
|
||||
DecodeEventType.CALL_GROUP, serviceOptions.toString(), message.getTimestamp());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
|
||||
if(message instanceof CapacityPlusWideAreaVoiceChannelUser)
|
||||
{
|
||||
|
||||
@@ -66,13 +66,14 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
|
||||
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
||||
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
||||
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
||||
private MBCAssembler mMBCAssembler;
|
||||
private PacketSequenceAssembler mPacketSequenceAssembler;
|
||||
private SLCAssembler mSLCAssembler = new SLCAssembler();
|
||||
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
|
||||
private Listener<IMessage> mMessageListener;
|
||||
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
||||
private DmrCrcMaskManager mCrcMaskManager = new DmrCrcMaskManager();
|
||||
private boolean mIgnoreCrcChecksums;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -80,6 +81,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
public DMRMessageProcessor(DecodeConfigDMR config)
|
||||
{
|
||||
mConfigDMR = config;
|
||||
mIgnoreCrcChecksums = config.getIgnoreCRCChecksums();
|
||||
mMBCAssembler = new MBCAssembler(mIgnoreCrcChecksums);
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
|
||||
{
|
||||
@@ -89,6 +92,17 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
mPacketSequenceAssembler = new PacketSequenceAssembler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
|
||||
*
|
||||
* @param message to check
|
||||
* @return true if ignore CRC checksums or if the message is valid.
|
||||
*/
|
||||
private boolean isValid(IMessage message)
|
||||
{
|
||||
return mIgnoreCrcChecksums || message.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary message processing
|
||||
*/
|
||||
@@ -128,7 +142,7 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
mSuperFrameProcessor2.process(voiceMessage);
|
||||
}
|
||||
}
|
||||
else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid())
|
||||
else if(message instanceof DMRBurst dmrBurst && isValid(dmrBurst))
|
||||
{
|
||||
if(dmrBurst.getTimeslot() == 1)
|
||||
{
|
||||
@@ -243,14 +257,14 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
|
||||
//Reset talker alias assembler on Idle or Terminator
|
||||
if(message.isValid() && (message instanceof IDLEMessage || message instanceof Terminator))
|
||||
if(isValid(message) && (message instanceof IDLEMessage || message instanceof Terminator))
|
||||
{
|
||||
mTalkerAliasAssembler.reset(message.getTimeslot());
|
||||
}
|
||||
}
|
||||
|
||||
//Assemble Talker Alias from FLC message fragments (header & blocks 1-3)
|
||||
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && message.isValid())
|
||||
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && isValid(message))
|
||||
{
|
||||
dispatch(mTalkerAliasAssembler.process(flc));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraTraff
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxGroupVoiceChannelUpdate;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusCSBKO_60;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowAnnouncement;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowGrant;
|
||||
@@ -277,7 +278,9 @@ public class CSBKMessageFactory
|
||||
case MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT:
|
||||
csbk = new CapacityPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
|
||||
case MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE:
|
||||
csbk = new CapacityMaxGroupVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_CSBKO_10:
|
||||
csbk = new ConnectPlusOTAAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
|
||||
@@ -77,7 +77,9 @@ public enum Opcode
|
||||
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT(Vendor.MOTOROLA_CONNECT_PLUS, 28, "ENHANCED DATA REVERT WINDOW ANNOUNCEMENT"),
|
||||
MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"),
|
||||
|
||||
MOTOROLA_CAPMAX_GROUP_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, 16, "GROUP CHANNEL USER"),
|
||||
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
|
||||
MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA_CAPACITY_PLUS, 33, "TALKGROUP ACTIVE"),
|
||||
|
||||
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"),
|
||||
MOTOROLA_CAPPLUS_CALL_ALERT_ACK(Vendor.MOTOROLA_CAPACITY_PLUS, 32, "CALL ALERT ACK"),
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Motorola Capacity Max - Group Voice Channel Update (Opcode 33 / 0x21)
|
||||
*
|
||||
* Likely incomplete implementation.
|
||||
*/
|
||||
public class CapacityMaxGroupVoiceChannelUpdate extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
{
|
||||
private static final int[] CHANNEL_NUMBER = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
|
||||
private static final int[] TIMESLOT = new int[]{28}; //Logical guess - not seeing it set in the example Tier3 recording
|
||||
|
||||
private static final int[] TALKGROUP = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||
private TalkgroupIdentifier mTalkgroup;
|
||||
private DMRTier3Channel mChannel;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param syncPattern for the CSBK
|
||||
* @param message bits
|
||||
* @param cach for the DMR burst
|
||||
* @param slotType for this message
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxGroupVoiceChannelUpdate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||
}
|
||||
|
||||
sb.append(" CSBK CAP-MAX GROUP VOICE CHANNEL UPDATE TALKGROUP:").append(getTalkgroup());
|
||||
sb.append(" CHANNEL:").append(getChannel());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup that is active on the channel.
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroup()
|
||||
{
|
||||
if(mTalkgroup == null)
|
||||
{
|
||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
|
||||
}
|
||||
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel the talkgroup is using.
|
||||
* @return channel.
|
||||
*/
|
||||
public DMRTier3Channel getChannel()
|
||||
{
|
||||
if(mChannel == null)
|
||||
{
|
||||
mChannel = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), getChannelTimeslot());
|
||||
}
|
||||
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timeslot
|
||||
*/
|
||||
private int getChannelTimeslot()
|
||||
{
|
||||
return getMessage().getInt(TIMESLOT) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a timeslot frequency map for the DMR channel
|
||||
*
|
||||
* @param timeslotFrequencies that match the logical timeslots
|
||||
*/
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
getChannel().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getChannel().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkgroup());
|
||||
mIdentifiers.add(getChannel());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,7 @@ public class TalkgroupVoiceChannelGrant extends ChannelGrant
|
||||
sb.append(" TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationTalkgroup());
|
||||
sb.append(" ").append(getChannel());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,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.CapacityMaxVoiceChannelUser;
|
||||
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.CapacityPlusWideAreaVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||
@@ -137,6 +138,9 @@ public class LCMessageFactory
|
||||
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
|
||||
flc = new MotorolaGroupVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
||||
flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
|
||||
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
|
||||
@@ -45,6 +45,7 @@ public enum LCOpcode
|
||||
|
||||
FULL_MOTOROLA_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"),
|
||||
FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 16, "CAPMAX 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"),
|
||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Capacity Max Voice Channel User (FLCO 16 / 0x10)
|
||||
*/
|
||||
public class CapacityMaxVoiceChannelUser extends CapacityPlusVoiceChannelUser
|
||||
{
|
||||
private static final int[] TALKGROUP = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
private static final int[] RADIO = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
|
||||
66, 67, 68, 69, 70, 71};
|
||||
|
||||
private TalkgroupIdentifier mTalkgroup;
|
||||
private RadioIdentifier mRadio;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param message for the link control payload
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxVoiceChannelUser(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 CAPMAX GROUP VOICE CHANNEL USER FM:");
|
||||
sb.append(getRadio());
|
||||
sb.append(" TO:").append(getTalkgroup());
|
||||
sb.append(" ").append(getServiceOptions());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup TO ID
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroup()
|
||||
{
|
||||
if(mTalkgroup == null)
|
||||
{
|
||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
|
||||
}
|
||||
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio FROM ID
|
||||
*/
|
||||
public RadioIdentifier getRadio()
|
||||
{
|
||||
if(mRadio == null)
|
||||
{
|
||||
mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO));
|
||||
}
|
||||
|
||||
return mRadio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkgroup());
|
||||
mIdentifiers.add(getRadio());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.mbc;
|
||||
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
||||
@@ -34,21 +35,35 @@ public class MBCAssembler
|
||||
private MBCHeader mTS2Header;
|
||||
private List<MBCContinuationBlock> mTS1ContinuationBlocks = new ArrayList<>();
|
||||
private List<MBCContinuationBlock> mTS2ContinuationBlocks = new ArrayList<>();
|
||||
private boolean mIgnoreCrcChecksums;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public MBCAssembler()
|
||||
public MBCAssembler(boolean ignoreCrcChecksums)
|
||||
{
|
||||
mIgnoreCrcChecksums = ignoreCrcChecksums;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
|
||||
* @param message to check
|
||||
* @return true if ignore CRC checksums or if the message is valid.
|
||||
*/
|
||||
private boolean isValid(IMessage message)
|
||||
{
|
||||
return mIgnoreCrcChecksums || message.isValid();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Processes the MBC header
|
||||
* @param header to process
|
||||
*/
|
||||
public void process(MBCHeader header)
|
||||
{
|
||||
if(header != null && header.isValid())
|
||||
if(header != null && isValid(header))
|
||||
{
|
||||
reset(header.getTimeslot());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user