diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java index 9bc95265..f5467547 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java @@ -381,6 +381,9 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection, Opcode opcode, long timestamp, boolean encrypted) { + //Apply the stored talker alias for the radio, learned from the radio's earlier calls + identifierCollection = mTalkerAliasManager.enrich(identifierCollection); + mLock.lock(); try @@ -419,11 +422,11 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I } else if(event != null) //Update current event { - Identifier from = getIdentifier(identifierCollection, Role.FROM); + Identifier from = getFrom(identifierCollection); if(from != null) { - Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM); + Identifier currentFrom = getFrom(event.getIdentifierCollection()); if(currentFrom != null && !Objects.equals(from, currentFrom)) { event.end(timestamp); @@ -660,6 +663,15 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I * @param collection containing a TO identifier * @return TO identifier or null */ + /** + * FROM identifier of the collection, preferring the radio over other FROM identifiers such as a talker alias. + */ + private Identifier getFrom(IdentifierCollection collection) + { + Identifier radio = TalkerAliasManager.getFromRadio(collection); + return radio != null ? radio : getIdentifier(collection, Role.FROM); + } + private Identifier getIdentifier(IdentifierCollection collection, Role role) { List identifiers = collection.getIdentifiers(role); diff --git a/src/main/java/io/github/dsheirer/module/decode/nxdn/NXDNTrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/nxdn/NXDNTrafficChannelManager.java index fdfaa22c..ac677ec2 100644 --- a/src/main/java/io/github/dsheirer/module/decode/nxdn/NXDNTrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/nxdn/NXDNTrafficChannelManager.java @@ -552,7 +552,8 @@ public class NXDNTrafficChannelManager extends TrafficChannelManager implements { NXDNChannelEventTracker tracker = getTrackerRemoveIfStale(frequency, timestamp); MutableIdentifierCollection ic = getMergedIdentifierCollection(identifiers); - mTalkerAliasManager.enrich(ic); + //Apply the stored talker alias for the radio (enrich() returns a copy, so update the collection in place) + mTalkerAliasManager.enrichMutable(ic); if(tracker != null) { if(tracker.isSameCallCheckingToAndFrom(ic, timestamp)) diff --git a/src/test/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManagerTalkerAliasTest.java b/src/test/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManagerTalkerAliasTest.java new file mode 100644 index 00000000..1bd9d03f --- /dev/null +++ b/src/test/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManagerTalkerAliasTest.java @@ -0,0 +1,92 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2026 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; + +import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierClass; +import io.github.dsheirer.identifier.MutableIdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.module.decode.dmr.channel.DMRAbsoluteChannel; +import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio; +import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; +import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests that DMR channel grant events show the stored talker alias of the radio, learned from its earlier calls. + */ +public class DMRTrafficChannelManagerTalkerAliasTest +{ + private static String getAlias(IDecodeEvent event) + { + Identifier alias = event.getIdentifierCollection().getIdentifier(IdentifierClass.USER, Form.TALKER_ALIAS, + Role.FROM); + return alias != null ? alias.getValue().toString() : null; + } + + private static MutableIdentifierCollection grant(int radio, int talkgroup) + { + MutableIdentifierCollection mic = new MutableIdentifierCollection(); + mic.update(DMRTalkgroup.create(talkgroup)); + mic.update(DMRRadio.createFrom(radio)); + return mic; + } + + @Test + public void channelGrantShowsTheStoredTalkerAlias() + { + DMRTrafficChannelManager manager = new DMRTrafficChannelManager(new Channel("Control")); + List events = new ArrayList<>(); + manager.addDecodeEventListener(event -> { + if(!events.contains(event)) + { + events.add(event); + } + }); + manager.getTalkerAliasManager().update((RadioIdentifier)DMRRadio.createFrom(100), + DmrTalkerAliasIdentifier.create("ENGINE 7")); + + //Radio 100 has a stored alias, radio 200 doesn't + manager.processChannelGrant(new DMRAbsoluteChannel(1, 1, 0, 0), grant(100, 10), + Opcode.STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT, 1000, false); + manager.processChannelGrant(new DMRAbsoluteChannel(2, 1, 0, 0), grant(200, 20), + Opcode.STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT, 1000, false); + + assertEquals(2, events.size()); + assertEquals("ENGINE 7", getAlias(events.get(0))); + assertNull(getAlias(events.get(1))); + + //Repeated grants for the same call update the event instead of starting a new one + manager.processChannelGrant(new DMRAbsoluteChannel(1, 1, 0, 0), grant(100, 10), + Opcode.STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT, 1500, false); + assertEquals(2, events.size()); + assertFalse(events.get(0).getDuration() < 0); + } +}