#1799 DMR Cap+ & CapMax now track events correctly. Updated Cap+ events to include channel descriptor and frequencies. Corrected Capacity Max talker aliases to combine base and continuation alias fragments into a unified alias identifier. (#1801)

Critical: resolved a thread deadlock issue when the DMR multi-frequency channel rotation thread deadlocks against the UI thread when accessing the current channel's frequency from the frequency controller.  This thread deadlock may be the source of multiple users reporting OutOfMemory crashes when running any DMR channel with multiple control frequencies where the decoder was rolling through each frequency checking for activity.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2024-01-21 07:47:37 -05:00
committed by GitHub
parent 1c420b8eee
commit 686bddf7d5
11 changed files with 398 additions and 300 deletions

View File

@@ -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.AliasModel;
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.DMRTrafficChannelManager;
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<Module> getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel,
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager)
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager,
IChannelDescriptor channelDescriptor)
{
List<Module> modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences, trafficChannelManager);
List<Module> modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences,
trafficChannelManager, channelDescriptor);
modules.addAll(getAuxiliaryDecoders(channel.getAuxDecodeConfiguration()));
return modules;
}
@@ -138,10 +142,12 @@ public class DecoderFactory
* @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<Module> getPrimaryModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel,
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager)
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager,
IChannelDescriptor channelDescriptor)
{
List<Module> modules = new ArrayList<Module>();
@@ -160,7 +166,7 @@ public class DecoderFactory
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 @@ public class DecoderFactory
*/
private static void processDMR(Channel channel, UserPreferences userPreferences, List<Module> modules,
AliasList aliasList, DecodeConfigDMR decodeConfig,
TrafficChannelManager trafficChannelManager)
TrafficChannelManager trafficChannelManager, IChannelDescriptor channelDescriptor)
{
modules.add(new DMRDecoder(decodeConfig));
@@ -437,6 +443,32 @@ public class DecoderFactory
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();

View File

@@ -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.IdentifierClass;
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.module.decode.ip.mototrbo.xcmp.XCMPPacket;
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 class DMRDecoderState extends TimeslotDecoderState
}
}
/**
* 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<Integer, IntegerIdentifier> idMap, Map<Integer, DMRLsn> lsnMap)
{
if(mCurrentCallEvent != null)
{
List<Identifier> 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<Integer,IntegerIdentifier> 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 @@ public class DMRDecoderState extends TimeslotDecoderState
//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 @@ public class DMRDecoderState extends TimeslotDecoderState
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 @@ public class DMRDecoderState extends TimeslotDecoderState
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 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(mCurrentCallEvent);
mCurrentCallEvent = null;
}
getIdentifierCollection().remove(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
}
@Override

View File

@@ -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.VoiceEMBMessage;
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 @@ public class DMRMessageProcessor implements Listener<IMessage>
//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<TimeslotFrequency> 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 @@ public class DMRMessageProcessor implements Listener<IMessage>
}
}
/**
* 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<TimeslotFrequency> 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
*/

View File

@@ -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.SourceConfigTuner;
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.Queue;
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 class DMRTrafficChannelManager extends TrafficChannelManager implements I
//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);

View File

@@ -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 @@ 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.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<Integer,IntegerIdentifier> mActiveIdentifierMap;
private Map<Integer,DMRLsn> mActiveLsnMap;
private DmrRestLsn mRestChannel;
private List<Identifier> mIdentifiers;
@@ -96,172 +108,202 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
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))
{
int a = 0; //debug
}
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)
{
int lowLsnBitmap = pointer;
pointer += 8;
for(int x = lowLsnBitmap; x < (lowLsnBitmap + 8); x++)
{
int lsn = x - lowLsnBitmap + 1;
if(getMessage().get(x))
{
hasIdentifier = true;
if(pointer <= 72)
{
talkgroups[lsn] = getMessage().getInt(BYTE, pointer);
pointer += 8;
}
else
{
talkgroups[lsn] = -1;
}
}
}
}
else
{
pointer += 8;
}
//Process voice LSNs 9 - 16
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
{
hasIdentifier = true;
int highLsnBitmap = pointer;
pointer += 8;
for(int x = highLsnBitmap; x < (highLsnBitmap + 8); x++)
{
int lsn = x - highLsnBitmap + 1 + 8;
if(getMessage().get(x))
{
if(pointer <= 72)
{
talkgroups[lsn] = getMessage().getInt(BYTE, pointer);
pointer += 8;
}
else
{
talkgroups[lsn] = -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;
//If we have the data revert channel bitmap ...
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
{
int lowDataLsnBitmap = pointer;
pointer += 8;
for(int x = lowDataLsnBitmap; x < (lowDataLsnBitmap + 8); x++)
{
int lsn = x - lowDataLsnBitmap + 1;
if(getMessage().get(x))
{
if(pointer <= 64)
{
radios[lsn] = getMessage().getInt(TWO_BYTES, pointer);
pointer += 16;
}
else
{
radios[lsn] = -1;
}
}
}
}
}
StringBuilder sb = new StringBuilder();
if(!hasIdentifier)
if(getActiveIdentifierMap().isEmpty())
{
sb.append("IDLE LSN: 1-16");
}
else
{
sb.append("ACTIVE LSN");
for(int x = 1; x < 17; x++)
{
if(talkgroups[x] != 0 || radios[x] != 0)
{
sb.append(" ").append(x);
sb.append(" ").append(x);
if(talkgroups[x] == 0)
if(mActiveIdentifierMap.containsKey(x))
{
Identifier id = mActiveIdentifierMap.get(x);
if(id instanceof TalkgroupIdentifier talkgroup)
{
if(radios[x] < 0)
if(talkgroup.getValue() > 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
sb.append(":(T)").append(talkgroup.getValue()); //Active talkgroup ID for channel.
}
else
{
sb.append(":"); //No radio or talkgroup active on channel
sb.append(":(T)A"); //Talkgroup active on channel but identifier is in a continuation message
}
}
else if(talkgroups[x] < 0)
else if(id instanceof RadioIdentifier radio)
{
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.
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
}
}
}
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<Integer,DMRLsn> getActiveLsnMap()
{
if(mActiveLsnMap == null)
{
getActiveIdentifierMap();
}
return mActiveLsnMap;
}
/**
* Map of active voice talkgroups and DMR logical slots where they are active.
*/
public Map<Integer,IntegerIdentifier> getActiveIdentifierMap()
{
if(mActiveIdentifierMap == null)
{
mActiveIdentifierMap = new HashMap<>();
mActiveLsnMap = new HashMap<>();
int pointer = LSN_1_8_BITMAP_START;
//Process voice LSNs 1-8
if(getMessage().getInt(BYTE, pointer) > 0)
{
int lowLsnBitmap = pointer;
pointer += 8;
for(int x = lowLsnBitmap; x < (lowLsnBitmap + 8); x++)
{
int lsn = x - lowLsnBitmap + 1;
if(getMessage().get(x))
{
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 voice LSNs 9 - 16
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
{
int highLsnBitmap = pointer;
pointer += 8;
for(int x = highLsnBitmap; x < (highLsnBitmap + 8); x++)
{
int lsn = x - highLsnBitmap + 1 + 8;
if(getMessage().get(x))
{
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))
{
pointer += 8;
//If we have the data revert channel bitmap ...
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
{
int lowDataLsnBitmap = pointer;
pointer += 8;
for(int x = lowDataLsnBitmap; x < (lowDataLsnBitmap + 8); x++)
{
int lsn = x - lowDataLsnBitmap + 1;
if(getMessage().get(x))
{
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));
}
}
}
}
}
}
return mActiveIdentifierMap;
}
/**
* Segment indicator for system status message values that are fragmented across multiple system status
@@ -301,7 +343,15 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
@Override
public int[] getLogicalChannelNumbers()
{
return getRestChannel().getLogicalChannelNumbers();
Set<Integer> 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 class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
getRestChannel().apply(timeslotFrequencies);
for(DMRLsn lsn: getActiveLsnMap().values())
{
lsn.apply(timeslotFrequencies);
}
}
@Override

View File

@@ -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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<Identifier> mIdentifiers;
/**
@@ -56,12 +74,12 @@ public class CapacityMaxTalkerAliasContinuation extends CapacityPlusVoiceChannel
* 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;