diff --git a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java index c090b36ec..e451c9fec 100644 --- a/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java +++ b/src/main/java/io/github/dsheirer/controller/channel/ChannelProcessingManager.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -442,7 +442,7 @@ else if(request.hasChildDecodeEventHistory()) /* Processing Modules */ List modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences, - request.getTrafficChannelManager()); + request.getTrafficChannelManager(), request.getChannelDescriptor()); processingChain.addModules(modules); //Post preload data from the request to the event bus. Modules that can handle preload data will annotate diff --git a/src/main/java/io/github/dsheirer/identifier/Form.java b/src/main/java/io/github/dsheirer/identifier/Form.java index f6c5a9ef4..dcc293927 100644 --- a/src/main/java/io/github/dsheirer/identifier/Form.java +++ b/src/main/java/io/github/dsheirer/identifier/Form.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -54,7 +54,6 @@ public enum Form STATE, SYSTEM, TALKER_ALIAS, - TALKER_ALIAS_2, TALKGROUP, TELEPHONE_NUMBER, TONE, diff --git a/src/main/java/io/github/dsheirer/identifier/alias/DmrTalkerAlias2Identifier.java b/src/main/java/io/github/dsheirer/identifier/alias/DmrTalkerAlias2Identifier.java deleted file mode 100644 index f689dd904..000000000 --- a/src/main/java/io/github/dsheirer/identifier/alias/DmrTalkerAlias2Identifier.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ***************************************************************************** - * 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.identifier.alias; - -import io.github.dsheirer.protocol.Protocol; - -/** - * DMR Talker alias (alternate/additional) value provided by the network for the current talker (ie FROM). - */ -public class DmrTalkerAlias2Identifier extends TalkerAlias2Identifier -{ - /** - * Constructs an instance. - * @param value of the talker alias - */ - public DmrTalkerAlias2Identifier(String value) - { - super(value); - } - - @Override - public boolean isValid() - { - return getValue() != null && !getValue().isEmpty(); - } - - @Override - public Protocol getProtocol() - { - return Protocol.DMR; - } - - public static DmrTalkerAlias2Identifier create(String value) - { - return new DmrTalkerAlias2Identifier(value); - } -} diff --git a/src/main/java/io/github/dsheirer/identifier/alias/TalkerAlias2Identifier.java b/src/main/java/io/github/dsheirer/identifier/alias/TalkerAlias2Identifier.java deleted file mode 100644 index 5d52285ea..000000000 --- a/src/main/java/io/github/dsheirer/identifier/alias/TalkerAlias2Identifier.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * ***************************************************************************** - * 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.identifier.alias; - -import io.github.dsheirer.identifier.Form; -import io.github.dsheirer.identifier.IdentifierClass; -import io.github.dsheirer.identifier.Role; -import io.github.dsheirer.identifier.string.StringIdentifier; - -/** - * Talker alias (alternate/second) value provided by the network for the current talker (ie FROM). - */ -public abstract class TalkerAlias2Identifier extends StringIdentifier -{ - /** - * Constructs an instance. - * @param value of the string/alias identifier - */ - public TalkerAlias2Identifier(String value) - { - super(value, IdentifierClass.USER, Form.TALKER_ALIAS_2, Role.FROM); - } - - @Override - public boolean isValid() - { - return getValue() != null && !getValue().isEmpty(); - } -} diff --git a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java index da388ee4d..3b7c392b1 100644 --- a/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java +++ b/src/main/java/io/github/dsheirer/module/decode/DecoderFactory.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -23,6 +23,7 @@ import io.github.dsheirer.alias.action.AliasActionManager; import io.github.dsheirer.audio.AbstractAudioModule; import io.github.dsheirer.audio.AudioModule; +import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.channel.state.State; import io.github.dsheirer.controller.channel.Channel; import io.github.dsheirer.controller.channel.Channel.ChannelType; @@ -48,7 +49,8 @@ import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR; import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; -import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; +import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet; import io.github.dsheirer.module.decode.event.DecodeEvent; import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder; @@ -123,9 +125,11 @@ public class DecoderFactory * @return list of configured decoders */ public static List getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel, - UserPreferences userPreferences, TrafficChannelManager trafficChannelManager) + UserPreferences userPreferences, TrafficChannelManager trafficChannelManager, + IChannelDescriptor channelDescriptor) { - List modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences, trafficChannelManager); + List modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences, + trafficChannelManager, channelDescriptor); modules.addAll(getAuxiliaryDecoders(channel.getAuxDecodeConfiguration())); return modules; } @@ -138,10 +142,12 @@ public static List getModules(ChannelMapModel channelMapModel, Channel c * @param aliasModel for alias lookups * @param userPreferences instance * @param trafficChannelManager optional traffic channel manager to use + * @param channelDescriptor to preload into the decoder state as the current channel. * @return list of modules to use for a processing chain */ public static List getPrimaryModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel, - UserPreferences userPreferences, TrafficChannelManager trafficChannelManager) + UserPreferences userPreferences, TrafficChannelManager trafficChannelManager, + IChannelDescriptor channelDescriptor) { List modules = new ArrayList(); @@ -160,7 +166,7 @@ public static List getPrimaryModules(ChannelMapModel channelMapModel, Ch break; case DMR: processDMR(channel, userPreferences, modules, aliasList, (DecodeConfigDMR)decodeConfig, - trafficChannelManager); + trafficChannelManager, channelDescriptor); break; case NBFM: processNBFM(channel, modules, aliasList, decodeConfig); @@ -413,7 +419,7 @@ private static void processAM(Channel channel, List modules, AliasList a */ private static void processDMR(Channel channel, UserPreferences userPreferences, List modules, AliasList aliasList, DecodeConfigDMR decodeConfig, - TrafficChannelManager trafficChannelManager) + TrafficChannelManager trafficChannelManager, IChannelDescriptor channelDescriptor) { modules.add(new DMRDecoder(decodeConfig)); @@ -437,6 +443,32 @@ private static void processDMR(Channel channel, UserPreferences userPreferences, DMRDecoderState state1 = new DMRDecoderState(channel, 1, dmrTrafficChannelManager); DMRDecoderState state2 = new DMRDecoderState(channel, 2, dmrTrafficChannelManager); + //Register the states with each other so that they can pass Cap+ site status messaging to resolve current channel + state1.setSisterDecoderState(state2); + state2.setSisterDecoderState(state1); + + //If an LSN is provided, apply it to both of the decoder states. + if(channelDescriptor instanceof DMRLsn lsn) + { + //If this is a REST descriptor, change it to a standard LSN descriptor. + if(channelDescriptor instanceof DmrRestLsn rest) + { + lsn = new DMRLsn(rest.getLsn()); + lsn.setTimeslotFrequency(rest.getTimeslotFrequency()); + } + + if(lsn.getTimeslot() == 1) + { + state1.setCurrentChannel(lsn); + state2.setCurrentChannel(lsn.getSisterTimeslot()); + } + else + { + state1.setCurrentChannel(lsn.getSisterTimeslot()); + state2.setCurrentChannel(lsn); + } + } + if(decodeConfig.hasChannelGrantEvent()) { DecodeEvent event = decodeConfig.getChannelGrantEvent(); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java index 88e09f5eb..0fbdcabca 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -33,19 +33,23 @@ import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.identifier.MutableIdentifierCollection; import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.log.LoggingSuppressor; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; import io.github.dsheirer.module.decode.dmr.event.DMRDecodeEvent; import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.DMRMessage; import io.github.dsheirer.module.decode.dmr.message.data.DataMessage; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraTrafficChannelTalkerStatus; +import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAdvantageModeVoiceChannelUpdate; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxOpenModeVoiceChannelUpdate; -import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAdvantageModeVoiceChannelUpdate; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusNeighbors; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusSiteStatus; import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusDataChannelGrant; @@ -97,12 +101,12 @@ import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest; import io.github.dsheirer.util.PacketUtil; +import java.util.List; +import java.util.Map; import org.jdesktop.swingx.mapviewer.GeoPosition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - /** * Decoder state for an DMR channel. Maintains the call/data/idle state of the channel and produces events by * monitoring the decoded message stream. @@ -120,6 +124,7 @@ public class DMRDecoderState extends TimeslotDecoderState private DecodeEvent mCurrentCallEvent; private long mCurrentFrequency; private boolean mIgnoreCRCChecksums; + private DMRDecoderState mSisterDecoderState; /** * Constructs an DMR decoder state with an optional traffic channel manager. @@ -147,6 +152,54 @@ public DMRDecoderState(Channel channel, int timeslot, DMRTrafficChannelManager t } } + /** + * Registers a reference to the decoder state that is processing the sisten timeslot. If this state is covering + * timeslot 1, then the argument will be the state covering timeslot 2, and vice-versa. + */ + public void setSisterDecoderState(DMRDecoderState state) + { + mSisterDecoderState = state; + } + + /** + * Processes a map of active talkgroups received from the sister timeslot decoder state so that we can recover + * the current channel identifier to enable populating call events with accurate current channel info. + * + * This is used for Capacity Plus systems to recover the current channel. + * + * @param idMap to process + * @param lsnMap to process + */ + public DMRChannel processActiveTalkgroups(Map idMap, Map lsnMap) + { + if(mCurrentCallEvent != null) + { + List toIds = mCurrentCallEvent.getIdentifierCollection().getIdentifiers(Role.TO); + + if(toIds.size() >= 1) + { + Identifier to = toIds.get(0); + + if(to instanceof TalkgroupIdentifier talkgroup) + { + Integer tgValue = talkgroup.getValue(); + + for(Map.Entry entry: idMap.entrySet()) + { + if(tgValue != null && tgValue.equals(entry.getValue().getValue())) + { + DMRLsn lsn = lsnMap.get(entry.getKey()); + setCurrentChannel(lsn); + return lsn; + } + } + } + } + } + + return null; + } + @Override protected void broadcast(IDecodeEvent event) { @@ -788,6 +841,20 @@ private void processCSBK(CSBKMessage csbk) //Update state and rest channel updateRestChannel(cpss.getRestChannel()); + + //If the sister timeslot hasn't identified the current channel, attempt to identify the channel + //from the current call activity map. + if(cpss.hasVoiceTalkgroups() && mSisterDecoderState != null && mSisterDecoderState.getCurrentChannel() == null) + { + DMRChannel sisterChannel = mSisterDecoderState + .processActiveTalkgroups(cpss.getActiveIdentifierMap(), cpss.getActiveLsnMap()); + + //If the returned channel is non-null, set our own channel + if(getCurrentChannel() == null && sisterChannel != null) + { + setCurrentChannel(sisterChannel.getSisterTimeslot()); + } + } } break; case MOTOROLA_CONPLUS_NEIGHBOR_REPORT: @@ -1098,7 +1165,22 @@ private void processLinkControl(LCMessage message, boolean isTerminator) case FULL_CAPACITY_MAX_TALKER_ALIAS: if(message instanceof CapacityMaxTalkerAlias alias) { - getIdentifierCollection().update(alias.getTalkerAliasIdentifier()); + //If we have a talker alias identifier, append this value. + Identifier existing = getIdentifierCollection().getIdentifier(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM); + + if(existing instanceof DmrTalkerAliasIdentifier talkerAlias && + !talkerAlias.equals(alias.getTalkerAliasIdentifier()) && + !talkerAlias.getValue().contains(alias.getTalkerAliasIdentifier().getValue())) + { + //Concatenate the existing talker alias fragment with the base alias value. + DmrTalkerAliasIdentifier updated = DmrTalkerAliasIdentifier + .create(alias.getTalkerAliasIdentifier().getValue() + talkerAlias.getValue()); + getIdentifierCollection().update(updated); + } + else + { + getIdentifierCollection().update(alias.getTalkerAliasIdentifier()); + } if(mCurrentCallEvent != null) { @@ -1109,7 +1191,23 @@ private void processLinkControl(LCMessage message, boolean isTerminator) case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION: if(message instanceof CapacityMaxTalkerAliasContinuation alias) { - getIdentifierCollection().update(alias.getTalkerAliasIdentifier()); + //If we have a talker alias identifier, append this value. + Identifier existing = getIdentifierCollection().getIdentifier(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM); + + if(existing instanceof DmrTalkerAliasIdentifier talkerAlias && + !talkerAlias.equals(alias.getTalkerAliasIdentifier()) && + !talkerAlias.getValue().contains(alias.getTalkerAliasIdentifier().getValue())) + { + //Concatenate the existing talker alias value with the updated continuation fragment. + DmrTalkerAliasIdentifier updated = DmrTalkerAliasIdentifier.create(talkerAlias.getValue() + + alias.getTalkerAliasIdentifier().getValue()); + getIdentifierCollection().update(updated); + } + else + { + //Temporarily place the continuation fragment alias into the identifier collection. + getIdentifierCollection().update(alias.getTalkerAliasIdentifier()); + } if(mCurrentCallEvent != null) { @@ -1335,6 +1433,8 @@ private void closeCurrentCallEvent(long timestamp) broadcast(mCurrentCallEvent); mCurrentCallEvent = null; } + + getIdentifierCollection().remove(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM); } @Override diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java index fb29ca5e7..fc19560eb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -49,13 +49,12 @@ import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage; import io.github.dsheirer.module.decode.dmr.message.voice.VoiceSuperFrameProcessor; import io.github.dsheirer.sample.Listener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Processes DMR messages and performs re-assembly of link control fragments @@ -159,30 +158,11 @@ else if(message instanceof DMRBurst dmrBurst && isValid(dmrBurst)) //Process data messages carrying a link control payload so that the LC payload can be processed/enriched if(message instanceof DataMessageWithLinkControl linkControlCarrier) { - receive(linkControlCarrier.getLCMessage()); + enrich(linkControlCarrier.getLCMessage()); } //Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings - if(message instanceof ITimeslotFrequencyReceiver) - { - ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message; - int[] lcns = receiver.getLogicalChannelNumbers(); - - List timeslotFrequencies = new ArrayList<>(); - - for(int lcn: lcns) - { - if(mTimeslotFrequencyMap.containsKey(lcn)) - { - timeslotFrequencies.add(mTimeslotFrequencyMap.get(lcn)); - } - } - - if(!timeslotFrequencies.isEmpty()) - { - receiver.apply(timeslotFrequencies); - } - } + enrich(message); //Now that the message has been (potentially) enriched, dispatch it to the modules dispatch(message); @@ -278,6 +258,34 @@ else if((message instanceof IDLEMessage || message instanceof Aloha || message i } } + /** + * Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings + * @param message to be enriched + */ + private void enrich(IMessage message) + { + if(message instanceof ITimeslotFrequencyReceiver) + { + ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message; + int[] lcns = receiver.getLogicalChannelNumbers(); + + List timeslotFrequencies = new ArrayList<>(); + + for(int lcn: lcns) + { + if(mTimeslotFrequencyMap.containsKey(lcn)) + { + timeslotFrequencies.add(mTimeslotFrequencyMap.get(lcn)); + } + } + + if(!timeslotFrequencies.isEmpty()) + { + receiver.apply(timeslotFrequencies); + } + } + } + /** * Dispatches the non-null message to the registered listener */ 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 35bd45eeb..c620952c8 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 @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -55,7 +55,6 @@ import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency; import io.github.dsheirer.source.tuner.channel.rotation.DisableChannelRotationMonitorRequest; import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -65,7 +64,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.ReentrantLock; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -267,7 +265,8 @@ public void convertToTrafficChannel(Channel channel, long currentFrequency, ICha //Dispatch request to persistently start the original channel with the rest channel frequency and reuse //this traffic channel manager in the new processing chain. - ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, this); + ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, restChannel, + null, this); request.setPersistentAttempt(true); request.setChildDecodeEventHistory(mTransientDecodeEventHistory); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java index 6a7c7651d..bef140fdf 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/csbk/motorola/CapacityPlusSiteStatus.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -21,15 +21,25 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage; import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.integer.IntegerIdentifier; +import io.github.dsheirer.identifier.radio.RadioIdentifier; +import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.module.decode.dmr.DMRSyncPattern; +import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn; 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.DMRRadio; +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.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Capacity+ Site Status CSBKO=62 Message @@ -43,6 +53,8 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq 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 Map mActiveIdentifierMap; + private Map mActiveLsnMap; private DmrRestLsn mRestChannel; private List mIdentifiers; @@ -96,173 +108,203 @@ public String toString() return sb.toString(); } - /** - * Indicates if this message has voice talkgroup activity. - * - * @return true if voice activity. - */ - private boolean hasVoiceTalkgroups() - { - return getSegmentIndicator().isFirst() && getMessage().getInt(LSN_VOICE_BITMAP) > 0; - } - /** * Extracts the call activity from the first or first/last fragment. * @return description of call activity. */ private String getActivityFragments() { - if(getMessage().get(48) && getMessage().get(56)) + StringBuilder sb = new StringBuilder(); + + if(getActiveIdentifierMap().isEmpty()) { - int a = 0; //debug + sb.append("IDLE LSN: 1-16"); } - 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 - if(getMessage().getInt(BYTE, pointer) > 0) + else { - int lowLsnBitmap = pointer; - pointer += 8; + sb.append("ACTIVE LSN"); - for(int x = lowLsnBitmap; x < (lowLsnBitmap + 8); x++) + for(int x = 1; x < 17; x++) { - int lsn = x - lowLsnBitmap + 1; + sb.append(" ").append(x); - if(getMessage().get(x)) + if(mActiveIdentifierMap.containsKey(x)) { - hasIdentifier = true; + Identifier id = mActiveIdentifierMap.get(x); - if(pointer <= 72) + if(id instanceof TalkgroupIdentifier talkgroup) { - talkgroups[lsn] = getMessage().getInt(BYTE, pointer); - pointer += 8; + if(talkgroup.getValue() > 0) + { + sb.append(":(T)").append(talkgroup.getValue()); //Active talkgroup ID for channel. + } + else + { + sb.append(":(T)A"); //Talkgroup active on channel but identifier is in a continuation message + } } - else + else if(id instanceof RadioIdentifier radio) { - talkgroups[lsn] = -1; + if(radio.getValue() > 0) + { + sb.append(":(R)").append(radio.getValue()); //Active radio ID for channel + } + else + { + sb.append(":(R)A"); //Radio active on channel but identifier is in a continuation message + } } } + else + { + sb.append(":"); //No radio or talkgroup active on channel + } } } - else + + return sb.toString(); + } + + /** + * Indicates if this message has voice talkgroup activity. + * + * @return true if voice activity. + */ + public boolean hasVoiceTalkgroups() + { + return getSegmentIndicator().isFirst() && getMessage().getInt(LSN_VOICE_BITMAP) > 0; + } + + /** + * Map of active LSNs. + * @return map of active LSNs + */ + public Map getActiveLsnMap() + { + if(mActiveLsnMap == null) { - pointer += 8; + getActiveIdentifierMap(); } - //Process voice LSNs 9 - 16 - if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0) + return mActiveLsnMap; + } + + /** + * Map of active voice talkgroups and DMR logical slots where they are active. + */ + public Map getActiveIdentifierMap() + { + if(mActiveIdentifierMap == null) { - hasIdentifier = true; - int highLsnBitmap = pointer; + mActiveIdentifierMap = new HashMap<>(); + mActiveLsnMap = new HashMap<>(); - pointer += 8; + int pointer = LSN_1_8_BITMAP_START; - for(int x = highLsnBitmap; x < (highLsnBitmap + 8); x++) + //Process voice LSNs 1-8 + if(getMessage().getInt(BYTE, pointer) > 0) { - int lsn = x - highLsnBitmap + 1 + 8; + int lowLsnBitmap = pointer; + pointer += 8; - if(getMessage().get(x)) + for(int x = lowLsnBitmap; x < (lowLsnBitmap + 8); x++) { - if(pointer <= 72) - { - talkgroups[lsn] = getMessage().getInt(BYTE, pointer); - pointer += 8; - } - else + int lsn = x - lowLsnBitmap + 1; + + if(getMessage().get(x)) { - talkgroups[lsn] = -1; + if(pointer <= 72) + { + mActiveLsnMap.put(lsn, new DMRLsn(lsn)); + mActiveIdentifierMap.put(lsn, DMRTalkgroup.create(getMessage().getInt(BYTE, pointer))); + pointer += 8; + } + else + { + //Create a placeholder for the talkgroup since we don't know the value + mActiveLsnMap.put(lsn, new DMRLsn(lsn)); + mActiveIdentifierMap.put(lsn, DMRTalkgroup.create(-1)); + } } } } - } - else - { - pointer += 8; - } - - //Process Radio IDs - first bit in radio options byte is set to indicate more activity - if(pointer <= 72 && getMessage().get(pointer)) - { - hasIdentifier = true; - pointer += 8; + else + { + pointer += 8; + } - //If we have the data revert channel bitmap ... + //Process voice LSNs 9 - 16 if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0) { - int lowDataLsnBitmap = pointer; + int highLsnBitmap = pointer; pointer += 8; - for(int x = lowDataLsnBitmap; x < (lowDataLsnBitmap + 8); x++) + for(int x = highLsnBitmap; x < (highLsnBitmap + 8); x++) { - int lsn = x - lowDataLsnBitmap + 1; + int lsn = x - highLsnBitmap + 1 + 8; if(getMessage().get(x)) { - if(pointer <= 64) + if(pointer <= 72) { - radios[lsn] = getMessage().getInt(TWO_BYTES, pointer); - pointer += 16; + mActiveLsnMap.put(lsn, new DMRLsn(lsn)); + mActiveIdentifierMap.put(lsn, DMRTalkgroup.create(getMessage().getInt(BYTE, pointer))); + pointer += 8; } else { - radios[lsn] = -1; + //Create a placeholder for the talkgroup since we don't know the value + mActiveLsnMap.put(lsn, new DMRLsn(lsn)); + mActiveIdentifierMap.put(lsn, DMRTalkgroup.create(-1)); } } } } - } - - StringBuilder sb = new StringBuilder(); + else + { + pointer += 8; + } - if(!hasIdentifier) - { - sb.append("IDLE LSN: 1-16"); - } - else - { - sb.append("ACTIVE LSN"); - for(int x = 1; x < 17; x++) + //Process Radio IDs - first bit in radio options byte is set to indicate more activity + if(pointer <= 72 && getMessage().get(pointer)) { - if(talkgroups[x] != 0 || radios[x] != 0) + pointer += 8; + + //If we have the data revert channel bitmap ... + if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0) { - sb.append(" ").append(x); + int lowDataLsnBitmap = pointer; + + pointer += 8; - if(talkgroups[x] == 0) + for(int x = lowDataLsnBitmap; x < (lowDataLsnBitmap + 8); x++) { - 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 + int lsn = x - lowDataLsnBitmap + 1; + + if(getMessage().get(x)) { - sb.append(":"); //No radio or talkgroup active on channel + if(pointer <= 64) + { + mActiveLsnMap.put(lsn, new DMRLsn(lsn)); + mActiveIdentifierMap.put(lsn, DMRRadio.createTo(getMessage().getInt(TWO_BYTES, pointer))); + pointer += 16; + } + else + { + //Create a placeholder for the radio since we don't know the value + mActiveLsnMap.put(lsn, new DMRLsn(lsn)); + mActiveIdentifierMap.put(lsn, DMRRadio.createTo(-1)); + } } } - 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. - } } } } - return sb.toString(); + return mActiveIdentifierMap; } - /** * Segment indicator for system status message values that are fragmented across multiple system status * messages. @@ -301,7 +343,15 @@ public int getRestLSN() @Override public int[] getLogicalChannelNumbers() { - return getRestChannel().getLogicalChannelNumbers(); + Set lcnSet = new HashSet<>(); + lcnSet.add(getRestChannel().getChannelNumber()); + + for(DMRLsn lsn: getActiveLsnMap().values()) + { + lcnSet.add(lsn.getChannelNumber()); + } + + return lcnSet.stream().mapToInt(v -> v).toArray(); } /** @@ -313,6 +363,10 @@ public int[] getLogicalChannelNumbers() public void apply(List timeslotFrequencies) { getRestChannel().apply(timeslotFrequencies); + for(DMRLsn lsn: getActiveLsnMap().values()) + { + lsn.apply(timeslotFrequencies); + } } @Override 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 index 53b2de633..badd68b06 100644 --- 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 @@ -1,9 +1,27 @@ +/* + * ***************************************************************************** + * 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.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 io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier; import java.util.ArrayList; import java.util.List; @@ -14,7 +32,7 @@ public class CapacityMaxTalkerAliasContinuation extends CapacityPlusVoiceChannel { private static final int ALIAS_START = 16; private static final int ALIAS_END = ALIAS_START + (7 * 8); - private DmrTalkerAlias2Identifier mTalkerAliasIdentifier; + private DmrTalkerAliasIdentifier mTalkerAliasIdentifier; private List mIdentifiers; /** @@ -56,12 +74,12 @@ public String toString() * Talker alias identifier * @return identifier */ - public DmrTalkerAlias2Identifier getTalkerAliasIdentifier() + public DmrTalkerAliasIdentifier getTalkerAliasIdentifier() { if(mTalkerAliasIdentifier == null) { String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim(); - mTalkerAliasIdentifier = DmrTalkerAlias2Identifier.create(alias); + mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(alias); } return mTalkerAliasIdentifier; diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java index 581258f20..952ea3e12 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerController.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer + * 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 @@ -273,19 +273,7 @@ public void setFrequency(long frequency) throws SourceException */ public long getFrequency() { - long frequency; - - try - { - getFrequencyControllerLock().lock(); - frequency = mFrequencyController.getFrequency(); - } - finally - { - getFrequencyControllerLock().unlock(); - } - - return frequency; + return mFrequencyController.getFrequency(); } @Override