diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java index 3d0f1b762..920c1e93c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java @@ -188,7 +188,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList private final PatchGroupManager mPatchGroupManager = new PatchGroupManager(); private final P25P1NetworkConfigurationMonitor mNetworkConfigurationMonitor; private final Listener mChannelEventListener; - private P25TrafficChannelManager mTrafficChannelManager; + private final P25TrafficChannelManager mTrafficChannelManager; private ServiceOptions mCurrentServiceOptions; /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java index a7609abf0..de61291af 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java @@ -465,9 +465,16 @@ private void processMacMessage(MacMessage message) case PHASE1_90_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED: if(mac instanceof GroupRegroupVoiceChannelUserAbbreviated gr) { - mPatchGroupManager.addPatchGroup(gr.getPatchgroup(), message.getTimestamp()); + if(gr.hasPatchgroup()) + { + mPatchGroupManager.addPatchGroup(gr.getPatchgroup(), message.getTimestamp()); + } + + if(gr.hasPatchgroup() || gr.hasRadio()) + { + processChannelUser(message, mac); + } } - processChannelUser(message, mac); break; /** diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java index c73a467af..646bebb25 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacMessageFactory.java @@ -129,6 +129,9 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.L3HarrisUnknownOpcode143; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.l3harris.UnknownOpcode136; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaAcknowledgeResponse; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaActiveGroupRadiosOpcode130_x82; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaActiveGroupRadiosOpcode143_x8F; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaActiveGroupRadiosOpcode191_xBF; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaDenyResponse; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupAddCommand; import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupChannelGrantExplicit; @@ -578,12 +581,18 @@ public static MacStructure createMacStructure(CorrectedBinaryMessage message, in return new MotorolaGroupRegroupVoiceChannelUserAbbreviated(message, offset); case MOTOROLA_81_GROUP_REGROUP_ADD: return new MotorolaGroupRegroupAddCommand(message, offset); + case MOTOROLA_82_ACTIVE_GROUP_RADIOS_130: + return new MotorolaActiveGroupRadiosOpcode130_x82(message, offset); case MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE: return new MotorolaGroupRegroupVoiceChannelUpdate(message, offset); case MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND: return new MotorolaGroupRegroupExtendedFunctionCommand(message, offset); case MOTOROLA_89_GROUP_REGROUP_DELETE: return new MotorolaGroupRegroupDeleteCommand(message, offset); + case MOTOROLA_8F_ACTIVE_GROUP_RADIOS_143: + return new MotorolaActiveGroupRadiosOpcode143_x8F(message, offset); + case MOTOROLA_BF_ACTIVE_GROUP_RADIOS_191: + return new MotorolaActiveGroupRadiosOpcode191_xBF(message, offset); case MOTOROLA_91_TALKER_ALIAS_HEADER: return new MotorolaTalkerAliasHeader(message, offset); case MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK: diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java index ae663935f..1fcc1d809 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/MacOpcode.java @@ -115,12 +115,15 @@ public enum MacOpcode MOTOROLA_80_GROUP_REGROUP_VOICE_CHANNEL_USER_ABBREVIATED(Vendor.MOTOROLA, 128, "MOTOROLA GROUP REGROUP VOICE CHANNEL USER ABBREVIATED", 8), MOTOROLA_81_GROUP_REGROUP_ADD(Vendor.MOTOROLA, 129, "MOTOROLA GROUP REGROUP ADD", 17), - MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA, 131, "MOTOROLA INDIVIDUAL REGROUP ACTIVE", 8), + MOTOROLA_82_ACTIVE_GROUP_RADIOS_130(Vendor.MOTOROLA, 130, "MOTOROLA ACTIVE GROUP RADIOS 130", Integer.MIN_VALUE), + MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA, 131, "MOTOROLA INDIVIDUAL REGROUP ACTIVE", 7), MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND(Vendor.MOTOROLA, 133, "MOTOROLA GROUP REGROUP", 11), MOTOROLA_89_GROUP_REGROUP_DELETE(Vendor.MOTOROLA, 137, "MOTOROLA GROUP REGROUP DELETE", 17), + MOTOROLA_8F_ACTIVE_GROUP_RADIOS_143(Vendor.MOTOROLA, 143, "MOTOROLA ACTIVE GROUP RADIOS 143", Integer.MIN_VALUE), + MOTOROLA_BF_ACTIVE_GROUP_RADIOS_191(Vendor.MOTOROLA, 191, "MOTOROLA ACTIVE GROUP RADIOS 191", Integer.MIN_VALUE), //Opcode 144 uses STANDARD vendor ID for some reason. - MOTOROLA_91_TALKER_ALIAS_HEADER(Vendor.MOTOROLA, 145, "MOTOROLA GROUP REGROUP UNKNOWN", 17), - MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK(Vendor.MOTOROLA, 149, "MOTOROLA UNKNOWN 149", 17), + MOTOROLA_91_TALKER_ALIAS_HEADER(Vendor.MOTOROLA, 145, "MOTOROLA TALKER ALIAS HEADER", 17), + MOTOROLA_95_TALKER_ALIAS_DATA_BLOCK(Vendor.MOTOROLA, 149, "MOTOROLA TALKER ALIAS DATA BLOCK", 17), MOTOROLA_A0_GROUP_REGROUP_VOICE_CHANNEL_USER_EXTENDED(Vendor.MOTOROLA, 160, "MOTOROLA GROUP REGROUP VOICE CHANNEL USER EXTENDED", 16), MOTOROLA_A3_GROUP_REGROUP_CHANNEL_GRANT_IMPLICIT(Vendor.MOTOROLA, 163, "MOTOROLA GROUP REGROUP CHANNEL GRANT IMPLICIT", 11), MOTOROLA_A4_GROUP_REGROUP_CHANNEL_GRANT_EXPLICIT(Vendor.MOTOROLA, 164, "MOTOROLA GROUP REGROUP CHANNEL GRANT EXPLICIT", 13), diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupRegroupVoiceChannelUserAbbreviated.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupRegroupVoiceChannelUserAbbreviated.java index ebbd97b55..87c8a6e7e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupRegroupVoiceChannelUserAbbreviated.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/GroupRegroupVoiceChannelUserAbbreviated.java @@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier; import io.github.dsheirer.module.decode.p25.reference.ServiceOptions; import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions; - import java.util.ArrayList; import java.util.List; @@ -64,11 +63,7 @@ public String toString() StringBuilder sb = new StringBuilder(); sb.append("GROUP REGROUP VOICE CHANNEL USER ABBREVIATED"); sb.append(" TALKGROUP:").append(getPatchgroup()); - if(hasRadio()) - { - sb.append(" TALKER RADIO:").append(getRadio()); - } - + sb.append(" TALKER RADIO:").append(getRadio()); return sb.toString(); } @@ -91,6 +86,14 @@ public PatchGroupIdentifier getPatchgroup() return mPatchgroup; } + /** + * Indicates if this message has a non-zero patch group identifier. + */ + public boolean hasPatchgroup() + { + return getInt(SUPERGROUP) > 0; + } + /** * Talker radio identifier. */ @@ -118,7 +121,11 @@ public List getIdentifiers() if(mIdentifiers == null) { mIdentifiers = new ArrayList<>(); - mIdentifiers.add(getPatchgroup()); + + if(hasPatchgroup()) + { + mIdentifiers.add(getPatchgroup()); + } if(hasRadio()) { diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode130_x82.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode130_x82.java new file mode 100644 index 000000000..132c96988 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode130_x82.java @@ -0,0 +1,211 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.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.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Group Radios Active Feature. Observed on NM-DTRS Phase 2 network. My theory is that this lists the + * subset of talkgroup member radios that are currently active on the traffic channel. + * + * Opcodes 130 and 143 are almost the same, except 143 is mostly transmitted during an ACTIVE call state and 130 is + * mostly transmitted during the HANGTIME state. I say mostly, because there are examples where both opcodes are seen + * in both channel states. + * + * Curiously: opcode 130 is in the middle of the message number sequence where GROUP REGROUP messages are allocated. + * + * Opcode 143 includes an additional 1-byte status field that seems to contain a 2-bit sequence counter that mostly + * cycles through all values, but sometimes it skips values or just toggles between two of the observed values. + * Examples: 0x00, 0x40, 0x80, 0xC0 + * There doesn't seem to be any correlation between the value of this nibble and the radio IDs being transmitted. + * + * The lower nibble of this 1-byte field seems to have an additional flag bit that is sometimes transmitted: + * Examples: 0x08, 0x88 + * + * Both opcode 130 and 143 employ up to 2x 1-byte fields that always transmit a 0x09 value. The second field is only + * present when the message is sending 3 or 4 radio IDs. + * + * It seems that the complete list of active member radios is transmitted in a fixed sequence with zero to four of those + * radios transmitted per message. The messages are transmitted when the talkgroup is first allocated to the channel + * and eventually the 130/143 messages stop listing radios and the empty 143 and the 191 messages are intermittently + * sent. Occasionally there are radios listed in the message with all zeros values. + * + * Opcode 143 is transmitted with just the status byte and no radio IDs after the full sequence of radios has been + * sent and are no longer being sent. + * + * I'm not sure what Opcode 191 indicates. It's always the same (3-bytes) value: BF9003. It may be some kind of + * feature marker to let the listening radios/devices know that the feature is active on that traffic channel. + * + * These three opcodes seem to be used for this feature. + * Opcode 130/x82 - talkgroup member radios currently active - mostly transmitted during HANGTIME + * Opcode 143/x8F - talkgroup member radios currently active - mostly transmitted during ACTIVE call state + * Opcode 191/xBF - transmitted continuously while channel is allocated to a talkgroup - fixed value: 0xBF9003 + */ +public class MotorolaActiveGroupRadiosOpcode130_x82 extends MacStructureVendor +{ + private static final IntField SEPARATOR_1 = IntField.length8(OCTET_4_BIT_24); //Value is always 0x09 + private static final IntField RADIO_1 = IntField.length24(OCTET_5_BIT_32); + private static final IntField RADIO_2 = IntField.length24(OCTET_8_BIT_56); + private static final IntField SEPARATOR_2 = IntField.length8(OCTET_11_BIT_80); //Value is always 0x09 + private static final IntField RADIO_3 = IntField.length24(OCTET_12_BIT_88); + private static final IntField RADIO_4 = IntField.length24(OCTET_15_BIT_112); + private RadioIdentifier mRadio1; + private RadioIdentifier mRadio2; + private RadioIdentifier mRadio3; + private RadioIdentifier mRadio4; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaActiveGroupRadiosOpcode130_x82(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA 130 ACTIVE GROUP RADIOS: "); + sb.append(getRadio1()); + + if(hasRadio2()) + { + sb.append(", ").append(getRadio2()); + + if(hasRadio3()) + { + sb.append(", ").append(getRadio3()); + + if(hasRadio4()) + { + sb.append(", ").append(getRadio4()); + } + } + } + + CorrectedBinaryMessage cbm = getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8)); + sb.append(" MSG:").append(cbm.toHexString()); + + return sb.toString(); + } + + /** + * Radio identifier or null if the message doesn't contain the value. + */ + public RadioIdentifier getRadio1() + { + if(mRadio1 == null && hasRadio1()) + { + mRadio1 = APCO25RadioIdentifier.createAny(getInt(RADIO_1)); + } + + return mRadio1; + } + + /** + * Radio identifier or null if the message doesn't contain the value. + */ + public RadioIdentifier getRadio2() + { + if(mRadio2 == null && hasRadio2()) + { + mRadio2 = APCO25RadioIdentifier.createAny(getInt(RADIO_2)); + } + + return mRadio2; + } + + /** + * Radio identifier or null if the message doesn't contain the value. + */ + public RadioIdentifier getRadio3() + { + if(mRadio3 == null && hasRadio3()) + { + mRadio3 = APCO25RadioIdentifier.createAny(getInt(RADIO_3)); + } + + return mRadio3; + } + + /** + * Radio identifier or null if the message doesn't contain the value. + */ + public RadioIdentifier getRadio4() + { + if(mRadio4 == null && hasRadio4()) + { + mRadio4 = APCO25RadioIdentifier.createAny(getInt(RADIO_4)); + } + + return mRadio4; + } + + /** + * Indicates if this message contains the radio value. + */ + public boolean hasRadio1() + { + return getLength() > 4; + } + + /** + * Indicates if this message contains the radio value. + */ + public boolean hasRadio2() + { + return getLength() > 8; + } + + /** + * Indicates if this message contains the radio value. + */ + public boolean hasRadio3() + { + return getLength() > 11; + } + + /** + * Indicates if this message contains the radio value. + */ + public boolean hasRadio4() + { + return getLength() > 15; + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode143_x8F.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode143_x8F.java new file mode 100644 index 000000000..567da1f05 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode143_x8F.java @@ -0,0 +1,232 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.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.module.decode.p25.identifier.radio.APCO25RadioIdentifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Group Radios Active Feature. Observed on NM-DTRS Phase 2 network. My theory is that this lists the + * subset of talkgroup member radios that are currently active on the traffic channel. + * + * Opcodes 130 and 143 are almost the same, except 143 is mostly transmitted during an ACTIVE call state and 130 is + * mostly transmitted during the HANGTIME state. I say mostly, because there are examples where both opcodes are seen + * in both channel states. + * + * Curiously: opcode 130 is in the middle of the message number sequence where GROUP REGROUP messages are allocated. + * + * Opcode 143 includes an additional 1-byte status field that seems to contain a 2-bit sequence counter that mostly + * cycles through all values, but sometimes it skips values or just toggles between two of the observed values. + * Examples: 0x00, 0x40, 0x80, 0xC0 + * There doesn't seem to be any correlation between the value of this nibble and the radio IDs being transmitted. + * + * The lower nibble of this 1-byte field seems to have an additional flag bit that is sometimes transmitted: + * Examples: 0x08, 0x88 + * + * Both opcode 130 and 143 employ up to 2x 1-byte fields that always transmit a 0x09 value. The second field is only + * present when the message is sending 3 or 4 radio IDs. + * + * It seems that the complete list of active member radios is transmitted in a fixed sequence with zero to four of those + * radios transmitted per message. The messages are transmitted when the talkgroup is first allocated to the channel + * and eventually the 130/143 messages stop listing radios and the empty 143 and the 191 messages are intermittently + * sent. Occasionally there are radios listed in the message with all zeros values. + * + * Opcode 143 is transmitted with just the status byte and no radio IDs after the full sequence of radios has been + * sent and are no longer being sent. + * + * I'm not sure what Opcode 191 indicates. It's always the same (3-bytes) value: BF9003. It may be some kind of + * feature marker to let the listening radios/devices know that the feature is active on that traffic channel. + * + * These three opcodes seem to be used for this feature. + * Opcode 130/x82 - talkgroup member radios currently active - mostly transmitted during HANGTIME + * Opcode 143/x8F - talkgroup member radios currently active - mostly transmitted during ACTIVE call state + * Opcode 191/xBF - transmitted continuously while channel is allocated to a talkgroup - fixed value: 0xBF9003 + */ +public class MotorolaActiveGroupRadiosOpcode143_x8F extends MacStructureVendor +{ + private static final IntField STATUS = IntField.length8(OCTET_4_BIT_24); + private static final IntField SEPARATOR_1 = IntField.length8(OCTET_5_BIT_32); + private static final IntField RADIO_1 = IntField.length24(OCTET_6_BIT_40); + private static final IntField RADIO_2 = IntField.length24(OCTET_9_BIT_64); + private static final IntField SEPARATOR_2 = IntField.length8(OCTET_12_BIT_88); + private static final IntField RADIO_3 = IntField.length24(OCTET_13_BIT_96); + private static final IntField RADIO_4 = IntField.length24(OCTET_16_BIT_120); + private RadioIdentifier mRadio1; + private RadioIdentifier mRadio2; + private RadioIdentifier mRadio3; + private RadioIdentifier mRadio4; + + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaActiveGroupRadiosOpcode143_x8F(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA 143 ACTIVE GROUP RADIOS: "); + + if(hasRadio1()) + { + sb.append(getRadio1()); + + if(hasRadio2()) + { + sb.append(", ").append(getRadio2()); + + if(hasRadio3()) + { + sb.append(", ").append(getRadio3()); + + if(hasRadio4()) + { + sb.append(", ").append(getRadio4()); + } + } + } + } + else + { + sb.append("NONE"); + } + + sb.append(" STATUS:").append(getStatusCode()); + + CorrectedBinaryMessage cbm = getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8)); + sb.append(" MSG:").append(cbm.toHexString()); + + return sb.toString(); + } + + /** + * Unknown status byte. First two bits seem to be a cycle counter 0x00, 0x40, 0x80, 0xC0. Second nibble, high + * bit observed to be set sometimes: 0x08, 0x88 + * @return code value in hex. + */ + public String getStatusCode() + { + return Integer.toHexString(getInt(STATUS)).toUpperCase(); + } + + /** + * Radio identifier or null if the message doesn't contain the value. + */ + public RadioIdentifier getRadio1() + { + if(mRadio1 == null && hasRadio1()) + { + mRadio1 = APCO25RadioIdentifier.createAny(getInt(RADIO_1)); + } + + return mRadio1; + } + + /** + * Radio identifier or null if the message doesn't contain the value. + */ + public RadioIdentifier getRadio2() + { + if(mRadio2 == null && hasRadio2()) + { + mRadio2 = APCO25RadioIdentifier.createAny(getInt(RADIO_2)); + } + + return mRadio2; + } + + /** + * Radio identifier or null if the message doesn't contain the value. + */ + public RadioIdentifier getRadio3() + { + if(mRadio3 == null && hasRadio3()) + { + mRadio3 = APCO25RadioIdentifier.createAny(getInt(RADIO_3)); + } + + return mRadio3; + } + + /** + * Radio identifier or null if the message doesn't contain the value. + */ + public RadioIdentifier getRadio4() + { + if(mRadio4 == null && hasRadio4()) + { + mRadio4 = APCO25RadioIdentifier.createAny(getInt(RADIO_4)); + } + + return mRadio4; + } + + /** + * Indicates if this message contains the radio value. + */ + public boolean hasRadio1() + { + return getLength() > 4; + } + + /** + * Indicates if this message contains the radio value. + */ + public boolean hasRadio2() + { + return getLength() > 8; + } + + /** + * Indicates if this message contains the radio value. + */ + public boolean hasRadio3() + { + return getLength() > 11; + } + + /** + * Indicates if this message contains the radio value. + */ + public boolean hasRadio4() + { + return getLength() > 15; + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode191_xBF.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode191_xBF.java new file mode 100644 index 000000000..324052201 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/message/mac/structure/motorola/MotorolaActiveGroupRadiosOpcode191_xBF.java @@ -0,0 +1,94 @@ +/* + * ***************************************************************************** + * 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 + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola; + +import io.github.dsheirer.bits.CorrectedBinaryMessage; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor; +import java.util.Collections; +import java.util.List; + +/** + * Motorola Group Radios Active Feature. Observed on NM-DTRS Phase 2 network. My theory is that this lists the + * subset of talkgroup member radios that are currently active on the traffic channel. + * + * Opcodes 130 and 143 are almost the same, except 143 is mostly transmitted during an ACTIVE call state and 130 is + * mostly transmitted during the HANGTIME state. I say mostly, because there are examples where both opcodes are seen + * in both channel states. + * + * Curiously: opcode 130 is in the middle of the message number sequence where GROUP REGROUP messages are allocated. + * + * Opcode 143 includes an additional 1-byte status field that seems to contain a 2-bit sequence counter that mostly + * cycles through all values, but sometimes it skips values or just toggles between two of the observed values. + * Examples: 0x00, 0x40, 0x80, 0xC0 + * There doesn't seem to be any correlation between the value of this nibble and the radio IDs being transmitted. + * + * The lower nibble of this 1-byte field seems to have an additional flag bit that is sometimes transmitted: + * Examples: 0x08, 0x48, 0x88 + * + * Both opcode 130 and 143 employ up to 2x 1-byte fields that always transmit a 0x09 value. The second field is only + * present when the message is sending 3 or 4 radio IDs. + * + * It seems that the complete list of active member radios is transmitted in a fixed sequence with zero to four of those + * radios transmitted per message. The messages are transmitted when the talkgroup is first allocated to the channel + * and eventually the 130/143 messages stop listing radios and the empty 143 and the 191 messages are intermittently + * sent. Occasionally there are radios listed in the message with all zeros values. + * + * Opcode 143 is transmitted with just the status byte and no radio IDs after the full sequence of radios has been + * sent and are no longer being sent. + * + * I'm not sure what Opcode 191 indicates. It's always the same (3-bytes) value: BF9003. It may be some kind of + * feature marker to let the listening radios/devices know that the feature is active on that traffic channel. + * + * These three opcodes seem to be used for this feature. + * Opcode 130/x82 - talkgroup member radios currently active - mostly transmitted during HANGTIME + * Opcode 143/x8F - talkgroup member radios currently active - mostly transmitted during ACTIVE call state + * Opcode 191/xBF - transmitted continuously while channel is allocated to a talkgroup - fixed value: 0xBF9003 + */ +public class MotorolaActiveGroupRadiosOpcode191_xBF extends MacStructureVendor +{ + /** + * Constructs the message + * + * @param message containing the message bits + * @param offset into the message for this structure + */ + public MotorolaActiveGroupRadiosOpcode191_xBF(CorrectedBinaryMessage message, int offset) + { + super(message, offset); + } + + /** + * Textual representation of this message + */ + public String toString() + { + CorrectedBinaryMessage cbm = getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8)); + StringBuilder sb = new StringBuilder(); + sb.append("MOTOROLA 191 ACTIVE GROUP RADIOS-FEATURE ACTIVE MSG:").append(cbm.toHexString()); + return sb.toString(); + } + + @Override + public List getIdentifiers() + { + return Collections.emptyList(); + } +}