getIdentifiers()
- {
- if(mIdentifiers == null)
- {
- mIdentifiers = new ArrayList<>();
- mIdentifiers.add(getTalkgroup());
- mIdentifiers.add(getChannel());
- }
-
- return mIdentifiers;
- }
-}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java
new file mode 100644
index 00000000..0e1df375
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityMaxOpenModeVoiceChannelUpdate.java
@@ -0,0 +1,233 @@
+/*
+ * *****************************************************************************
+ * 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
+ * ****************************************************************************
+ */
+
+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 Open System Mode - Voice Channel Update (Opcode 33 / 0x21)
+ *
+ * Opcode 33 and 34 are very similar. However, Opcode 33 appears to support full 24-bit group identifiers whereas
+ * Opcode 34 only supports 10-bit group identifiers.
+ *
+ * Likely incomplete implementation.
+ */
+public class CapacityMaxOpenModeVoiceChannelUpdate 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[] TALKGROUP_TS1 = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
+ private static final int[] TALKGROUP_TS2 = new int[]{56, 57, 58, 59, 60, 61, 63, 64, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
+ private TalkgroupIdentifier mTalkgroupTS1;
+ private TalkgroupIdentifier mTalkgroupTS2;
+ private DMRTier3Channel mChannelTS1;
+ private DMRTier3Channel mChannelTS2;
+ private List 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 CapacityMaxOpenModeVoiceChannelUpdate(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 MODE:OPEN-SYSTEM CHANNEL UPDATE");
+
+ if(hasTimeslot1())
+ {
+ sb.append(" TG1:").append(getTalkgroupTS1());
+ sb.append(" ON").append(getChannelTS1());
+ }
+ if(hasTimeslot2())
+ {
+ sb.append(" TG2:").append(getTalkgroupTS2());
+ sb.append(" ON").append(getChannelTS2());
+ }
+
+ sb.append(" MSG:").append(getMessage().toHexString());
+ return sb.toString();
+ }
+
+ /**
+ * Indicates if this message contains channel update for timeslot 1.
+ */
+ public boolean hasTimeslot1()
+ {
+ return getMessage().getInt(TALKGROUP_TS1) > 0;
+ }
+
+ /**
+ * Indicates if this message contains channel update for timeslot 2.
+ */
+ public boolean hasTimeslot2()
+ {
+ return getMessage().getInt(TALKGROUP_TS2) > 0;
+ }
+
+ /**
+ * Talkgroup that is active on the channel for timeslot 1.
+ */
+ public TalkgroupIdentifier getTalkgroupTS1()
+ {
+ if(mTalkgroupTS1 == null)
+ {
+ mTalkgroupTS1 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_TS1));
+ }
+
+ return mTalkgroupTS1;
+ }
+
+ /**
+ * Talkgroup that is active on the channel for timeslot 2.
+ */
+ public TalkgroupIdentifier getTalkgroupTS2()
+ {
+ if(mTalkgroupTS2 == null)
+ {
+ mTalkgroupTS2 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_TS2));
+ }
+
+ return mTalkgroupTS2;
+ }
+
+ /**
+ * Channel for timeslot 1
+ */
+ public DMRTier3Channel getChannelTS1()
+ {
+ if(mChannelTS1 == null)
+ {
+ mChannelTS1 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), 1);
+ }
+
+ return mChannelTS1;
+ }
+
+ /**
+ * Channel for timeslot 2
+ */
+ public DMRTier3Channel getChannelTS2()
+ {
+ if(mChannelTS2 == null)
+ {
+ mChannelTS2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), 2);
+ }
+
+ return mChannelTS2;
+ }
+
+ /**
+ * Assigns a timeslot frequency map for the DMR channel
+ *
+ * @param timeslotFrequencies that match the logical timeslots
+ */
+ @Override
+ public void apply(List timeslotFrequencies)
+ {
+ if(hasTimeslot1())
+ {
+ getChannelTS1().apply(timeslotFrequencies);
+ }
+ if(hasTimeslot2())
+ {
+ getChannelTS2().apply(timeslotFrequencies);
+ }
+ }
+
+ @Override
+ public int[] getLogicalChannelNumbers()
+ {
+ boolean ts1 = hasTimeslot1();
+ boolean ts2 = hasTimeslot2();
+
+ if(ts1 && ts2)
+ {
+ return new int[]{getChannelTS1().getChannelNumber(), getChannelTS2().getChannelNumber()};
+ }
+ else if(ts1)
+ {
+ return new int[]{getChannelTS1().getChannelNumber()};
+ }
+ else if(ts2)
+ {
+ return new int[]{getChannelTS2().getChannelNumber()};
+ }
+
+ return new int[0];
+ }
+
+ @Override
+ public List getIdentifiers()
+ {
+ if(mIdentifiers == null)
+ {
+ mIdentifiers = new ArrayList<>();
+
+ if(hasTimeslot1())
+ {
+ mIdentifiers.add(getChannelTS1());
+ mIdentifiers.add(getTalkgroupTS1());
+ }
+ if(hasTimeslot2())
+ {
+ mIdentifiers.add(getChannelTS2());
+ mIdentifiers.add(getTalkgroupTS2());
+ }
+ }
+
+ return mIdentifiers;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java
index 0e98ea91..18922ded 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCMessageFactory.java
@@ -37,6 +37,8 @@ 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.CapacityMaxTalkerAlias;
+import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAliasContinuation;
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;
@@ -141,6 +143,12 @@ public class LCMessageFactory
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot);
break;
+ case FULL_CAPACITY_MAX_TALKER_ALIAS:
+ flc = new CapacityMaxTalkerAlias(message, timestamp, timeslot);
+ break;
+ case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
+ flc = new CapacityMaxTalkerAliasContinuation(message, timestamp, timeslot);
+ break;
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
break;
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java
index 798a3ceb..a6fe7ed5 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/LCOpcode.java
@@ -46,6 +46,8 @@ 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"),
+ FULL_CAPACITY_MAX_TALKER_ALIAS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 20, "CAPMAX TALKER ALIAS"),
+ FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION(Vendor.MOTOROLA_CAPACITY_PLUS, true, 21, "CAPMAX TALKER ALIAS CONTINUATION"),
//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
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxTalkerAlias.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxTalkerAlias.java
new file mode 100644
index 00000000..ef8b0ad8
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxTalkerAlias.java
@@ -0,0 +1,92 @@
+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.alias.DmrTalkerAliasIdentifier;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Capacity Max Talker Alias (FLCO 20 / FID 0x10)
+ *
+ * See also: FLCO 21 - Talker Alias Continuation
+ */
+public class CapacityMaxTalkerAlias extends CapacityPlusVoiceChannelUser
+{
+ private static final int[] ENCODING_TYPE = new int[]{16, 17, 18};
+ private static final int[] LENGTH = new int[]{19, 20, 21, 22};
+ private static final int ALIAS_START = 24;
+ private static final int ALIAS_END = ALIAS_START + (6 * 8); //6 characters?
+ private DmrTalkerAliasIdentifier mTalkerAliasIdentifier;
+ private List mIdentifiers;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param message for the link control payload
+ * @param timestamp
+ * @param timeslot
+ */
+ public CapacityMaxTalkerAlias(CorrectedBinaryMessage message, long timestamp, int timeslot)
+ {
+ super(message, timestamp, timeslot);
+ }
+
+ public int getLength()
+ {
+ return getMessage().getInt(LENGTH);
+ }
+
+ @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 TALKER ALIAS:").append(getTalkerAliasIdentifier());
+ sb.append(" LENGTH:").append(getLength());
+ sb.append(" MSG:").append(getMessage().toHexString());
+ return sb.toString();
+ }
+
+ /**
+ * Talker alias identifier
+ *
+ * @return identifier
+ */
+ public DmrTalkerAliasIdentifier getTalkerAliasIdentifier()
+ {
+ if(mTalkerAliasIdentifier == null)
+ {
+ String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim();
+ mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(alias);
+ }
+
+ return mTalkerAliasIdentifier;
+ }
+
+ @Override
+ public List getIdentifiers()
+ {
+ if(mIdentifiers == null)
+ {
+ mIdentifiers = new ArrayList<>();
+ mIdentifiers.add(getTalkerAliasIdentifier());
+ }
+
+ return mIdentifiers;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxTalkerAliasContinuation.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxTalkerAliasContinuation.java
new file mode 100644
index 00000000..53b2de63
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/motorola/CapacityMaxTalkerAliasContinuation.java
@@ -0,0 +1,81 @@
+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.alias.DmrTalkerAlias2Identifier;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Capacity Max Talker Alias Continuation (FLCO 21 / FID 0x10)
+ */
+public class CapacityMaxTalkerAliasContinuation extends CapacityPlusVoiceChannelUser
+{
+ private static final int ALIAS_START = 16;
+ private static final int ALIAS_END = ALIAS_START + (7 * 8);
+ private DmrTalkerAlias2Identifier mTalkerAliasIdentifier;
+ private List mIdentifiers;
+
+ /**
+ * Constructs an instance.
+ *
+ * @param message for the link control payload
+ * @param timestamp
+ * @param timeslot
+ */
+ public CapacityMaxTalkerAliasContinuation(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 TALKER ALIAS CONTINUED:").append(getTalkerAliasIdentifier());
+ sb.append(" MSG:").append(getMessage().toHexString());
+ return sb.toString();
+ }
+
+ /**
+ * Talker alias identifier
+ * @return identifier
+ */
+ public DmrTalkerAlias2Identifier getTalkerAliasIdentifier()
+ {
+ if(mTalkerAliasIdentifier == null)
+ {
+ String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim();
+ mTalkerAliasIdentifier = DmrTalkerAlias2Identifier.create(alias);
+ }
+
+ return mTalkerAliasIdentifier;
+ }
+
+ @Override
+ public List getIdentifiers()
+ {
+ if(mIdentifiers == null)
+ {
+ mIdentifiers = new ArrayList<>();
+ mIdentifiers.add(getTalkerAliasIdentifier());
+ }
+
+ return mIdentifiers;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ShortLCMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ShortLCMessage.java
index b9fa8ca8..6568e6b3 100644
--- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ShortLCMessage.java
+++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/shorty/ShortLCMessage.java
@@ -88,7 +88,8 @@ public abstract class ShortLCMessage extends LCMessage implements IMessage
{
Vendor vendor = null;
- if(binaryMessage.get(VENDOR_FLAG))
+ //If the first bit is set, consider this a vendor variant, unless the opcode is 10 for Connect Plus Control Channel.
+ if(binaryMessage.get(VENDOR_FLAG) && binaryMessage.getInt(OPCODE) != 10)
{
vendor = Vendor.fromValue(binaryMessage.getInt(VENDOR));
}
diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java
index 8e0d59eb..3ce817b7 100644
--- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java
+++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java
@@ -148,6 +148,15 @@ public enum DecodeEventType
DATA_CALLS.contains(this) || REGISTRATION.contains(this);
}
+ /**
+ * Indicates if this is a call event.
+ * @return true if this is a call event type.
+ */
+ public boolean isCallEvent()
+ {
+ return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || DATA_CALLS.contains(this);
+ }
+
/**
* Label or pretty value for the element
* @return label