mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 17:47:32 -05:00
#1787 Adds CapMax talker alias continuation FLCO. Resolves issue with Con+ control channel SLCO message detection. (#1796)
#1787 Adds Capacity Max talker alias FLCO. Fixes standard FLC Talker Alias using ISO7 format. Updates channel details to list the tier 3 neighbors in the details. #1787 Updates DMR capacity max event handling to better show events. Adds support for voice channel update for both Open System and Advantage modes. May cause issues with Cap+ or Con+ due to changes ... needs more testing. #1787 DMR capacity max group voice update handling. Co-authored-by: sheirerd <sheirerd@ainfosec.com>
This commit is contained in:
@@ -307,8 +307,8 @@ public class BinaryMessage extends BitSet
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides the parent method which returns a BitSet so that we can return a BinaryMessage
|
* Overrides the parent method which returns a BitSet so that we can return a BinaryMessage
|
||||||
* @param from
|
* @param from inclusive
|
||||||
* @param to
|
* @param to exclusive
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -1198,9 +1198,22 @@ public class BinaryMessage extends BitSet
|
|||||||
{
|
{
|
||||||
byte[] bytes = new byte[characterCount];
|
byte[] bytes = new byte[characterCount];
|
||||||
|
|
||||||
|
int index;
|
||||||
for(int x = 0; x < characterCount; x++)
|
for(int x = 0; x < characterCount; x++)
|
||||||
{
|
{
|
||||||
bytes[x] = getByte(CHARACTER_7_BIT, x * 7 + offset);
|
index = x * 7 + offset;
|
||||||
|
|
||||||
|
if(index == 0)
|
||||||
|
{
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, index);
|
||||||
|
bytes[x] = (byte)((bytes[x] >> 1) & 0x7F);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index--;
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, index);
|
||||||
|
bytes[x] &= (byte)0x7F; //mask off the high bit since we have to parse it as an 8-bit byte.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new String(bytes);
|
return new String(bytes);
|
||||||
|
|||||||
@@ -241,7 +241,8 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
String value = id.getValue().replace(" TS0", "").replace(" TS1", "");
|
String value = id.getValue().replace(" TS1", "")
|
||||||
|
.replace(" TS2", "");
|
||||||
return value + " TS:" + channelMetadata.getTimeslot();
|
return value + " TS:" + channelMetadata.getTimeslot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public abstract class DecoderState extends AbstractDecoderState
|
|||||||
/**
|
/**
|
||||||
* Sets the current channel descriptor
|
* Sets the current channel descriptor
|
||||||
*/
|
*/
|
||||||
protected void setCurrentChannel(IChannelDescriptor channel)
|
public void setCurrentChannel(IChannelDescriptor channel)
|
||||||
{
|
{
|
||||||
mCurrentChannel = channel;
|
mCurrentChannel = channel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ public enum Form
|
|||||||
STATE,
|
STATE,
|
||||||
SYSTEM,
|
SYSTEM,
|
||||||
TALKER_ALIAS,
|
TALKER_ALIAS,
|
||||||
|
TALKER_ALIAS_2,
|
||||||
TALKGROUP,
|
TALKGROUP,
|
||||||
TELEPHONE_NUMBER,
|
TELEPHONE_NUMBER,
|
||||||
TONE,
|
TONE,
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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 <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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 <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,10 @@ import io.github.dsheirer.module.decode.dmr.DMRDecoderState;
|
|||||||
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
|
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
|
||||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||||
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
|
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.message.filter.DmrMessageFilterSet;
|
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;
|
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
|
||||||
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState;
|
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState;
|
||||||
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
|
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
|
||||||
@@ -416,22 +419,50 @@ public class DecoderFactory
|
|||||||
|
|
||||||
DMRTrafficChannelManager dmrTrafficChannelManager = null;
|
DMRTrafficChannelManager dmrTrafficChannelManager = null;
|
||||||
|
|
||||||
|
if(trafficChannelManager instanceof DMRTrafficChannelManager)
|
||||||
|
{
|
||||||
|
dmrTrafficChannelManager = (DMRTrafficChannelManager)trafficChannelManager;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dmrTrafficChannelManager = new DMRTrafficChannelManager(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Only register the traffic channel manager as a module if this is the parent control channel.
|
||||||
if(channel.isStandardChannel())
|
if(channel.isStandardChannel())
|
||||||
{
|
{
|
||||||
if(trafficChannelManager instanceof DMRTrafficChannelManager)
|
|
||||||
{
|
|
||||||
dmrTrafficChannelManager = (DMRTrafficChannelManager)trafficChannelManager;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dmrTrafficChannelManager = new DMRTrafficChannelManager(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
modules.add(dmrTrafficChannelManager);
|
modules.add(dmrTrafficChannelManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
modules.add(new DMRDecoderState(channel, 1, dmrTrafficChannelManager));
|
DMRDecoderState state1 = new DMRDecoderState(channel, 1, dmrTrafficChannelManager);
|
||||||
modules.add(new DMRDecoderState(channel, 2, dmrTrafficChannelManager));
|
DMRDecoderState state2 = new DMRDecoderState(channel, 2, dmrTrafficChannelManager);
|
||||||
|
|
||||||
|
if(decodeConfig.hasChannelGrantEvent())
|
||||||
|
{
|
||||||
|
DecodeEvent event = decodeConfig.getChannelGrantEvent();
|
||||||
|
|
||||||
|
if(decodeConfig.getChannelGrantEvent().getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
state1.setCurrentCallEvent(event);
|
||||||
|
|
||||||
|
if(event.getChannelDescriptor() instanceof DMRChannel dmrChannel)
|
||||||
|
{
|
||||||
|
state2.setCurrentChannel(dmrChannel.getSisterTimeslot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state2.setCurrentCallEvent(event);
|
||||||
|
|
||||||
|
if(event.getChannelDescriptor() instanceof DMRChannel dmrChannel)
|
||||||
|
{
|
||||||
|
state1.setCurrentChannel(dmrChannel.getSisterTimeslot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modules.add(state1);
|
||||||
|
modules.add(state2);
|
||||||
modules.add(new DMRAudioModule(userPreferences, aliasList, 1));
|
modules.add(new DMRAudioModule(userPreferences, aliasList, 1));
|
||||||
modules.add(new DMRAudioModule(userPreferences, aliasList, 2));
|
modules.add(new DMRAudioModule(userPreferences, aliasList, 2));
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import io.github.dsheirer.identifier.IdentifierClass;
|
|||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
import io.github.dsheirer.log.LoggingSuppressor;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.module.decode.DecoderType;
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||||
@@ -44,6 +44,8 @@ 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.CSBKMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraTrafficChannelTalkerStatus;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraTrafficChannelTalkerStatus;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
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.CapacityPlusNeighbors;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusSiteStatus;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusSiteStatus;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusDataChannelGrant;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusDataChannelGrant;
|
||||||
@@ -66,6 +68,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasComp
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAlias;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAliasContinuation;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
@@ -81,6 +85,7 @@ import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParam
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
|
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
|
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||||
@@ -92,13 +97,12 @@ import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
|
|||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||||
import io.github.dsheirer.util.PacketUtil;
|
import io.github.dsheirer.util.PacketUtil;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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
|
* Decoder state for an DMR channel. Maintains the call/data/idle state of the channel and produces events by
|
||||||
* monitoring the decoded message stream.
|
* monitoring the decoded message stream.
|
||||||
@@ -106,16 +110,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class DMRDecoderState extends TimeslotDecoderState
|
public class DMRDecoderState extends TimeslotDecoderState
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DMRDecoderState.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DMRDecoderState.class);
|
||||||
|
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog);
|
||||||
private static final long MAX_VALID_CALL_DURATION_MS = 30000;
|
private static final long MAX_VALID_CALL_DURATION_MS = 30000;
|
||||||
|
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
||||||
|
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
||||||
private Channel mChannel;
|
private Channel mChannel;
|
||||||
private DMRNetworkConfigurationMonitor mNetworkConfigurationMonitor;
|
private DMRNetworkConfigurationMonitor mNetworkConfigurationMonitor;
|
||||||
private DMRTrafficChannelManager mTrafficChannelManager;
|
private DMRTrafficChannelManager mTrafficChannelManager;
|
||||||
private DecodeEvent mCurrentCallEvent;
|
private DecodeEvent mCurrentCallEvent;
|
||||||
private long mCurrentFrequency;
|
private long mCurrentFrequency;
|
||||||
private boolean mIgnoreCRCChecksums;
|
private boolean mIgnoreCRCChecksums;
|
||||||
private Map<DMRChannel,DecodeEvent> mDetectedCallEventsMap = new HashMap<>();
|
|
||||||
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
|
||||||
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an DMR decoder state with an optional traffic channel manager.
|
* Constructs an DMR decoder state with an optional traffic channel manager.
|
||||||
@@ -143,6 +147,17 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void broadcast(IDecodeEvent event)
|
||||||
|
{
|
||||||
|
super.broadcast(event);
|
||||||
|
|
||||||
|
if(mChannel.isTrafficChannel() && hasTrafficChannelManager())
|
||||||
|
{
|
||||||
|
mTrafficChannelManager.receiveTrafficChannelEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
|
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
|
||||||
* @param message to check
|
* @param message to check
|
||||||
@@ -161,6 +176,16 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
return mTrafficChannelManager != null;
|
return mTrafficChannelManager != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the initial call event for a traffic channel.
|
||||||
|
* @param decodeEvent to use as the initial call event.
|
||||||
|
*/
|
||||||
|
public void setCurrentCallEvent(DecodeEvent decodeEvent)
|
||||||
|
{
|
||||||
|
mCurrentCallEvent = decodeEvent;
|
||||||
|
setCurrentChannel(decodeEvent.getChannelDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes channel configuration change notifications received over the processing chain event bus. This is
|
* Processes channel configuration change notifications received over the processing chain event bus. This is
|
||||||
* primarily used for Capacity+ systems when the standard channel is converted to a traffic channel. In response,
|
* primarily used for Capacity+ systems when the standard channel is converted to a traffic channel. In response,
|
||||||
@@ -197,7 +222,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
resetState();
|
resetState();
|
||||||
|
|
||||||
mCurrentFrequency = 0;
|
mCurrentFrequency = 0;
|
||||||
mDetectedCallEventsMap.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -307,6 +331,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||||
|
|
||||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.details("MESSAGE: " + sms.getSMS())
|
.details("MESSAGE: " + sms.getSMS())
|
||||||
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
@@ -331,6 +356,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
mic.update(hyteraSmsPacket.getDestination());
|
mic.update(hyteraSmsPacket.getDestination());
|
||||||
|
|
||||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details("SMS:" + hyteraSmsPacket.getSMS())
|
.details("SMS:" + hyteraSmsPacket.getSMS())
|
||||||
@@ -346,6 +372,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
sb.append("HYTERA RRS REGISTER RADIO:");
|
sb.append("HYTERA RRS REGISTER RADIO:");
|
||||||
sb.append(rrs.getDestination());
|
sb.append(rrs.getDestination());
|
||||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(sb.toString())
|
.details(sb.toString())
|
||||||
@@ -371,6 +398,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
|
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
|
||||||
|
|
||||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(sb.toString())
|
.details(sb.toString())
|
||||||
@@ -383,6 +411,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details("HYTERA LONG DATA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
.details("HYTERA LONG DATA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||||
@@ -395,6 +424,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(ars.toString())
|
.details(ars.toString())
|
||||||
@@ -407,6 +437,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp())
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(lrrp.toString())
|
.details(lrrp.toString())
|
||||||
@@ -419,6 +450,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp())
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(xcmp.toString())
|
.details(xcmp.toString())
|
||||||
@@ -428,6 +460,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(packet.toString())
|
.details(packet.toString())
|
||||||
@@ -760,15 +793,17 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
|
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
|
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||||
case STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT:
|
case STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT:
|
||||||
|
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||||
case STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
case STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
||||||
|
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||||
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM:
|
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM:
|
||||||
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
||||||
if(csbk instanceof ChannelGrant)
|
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||||
|
if(csbk instanceof ChannelGrant channelGrant)
|
||||||
{
|
{
|
||||||
ChannelGrant dataGrant = (ChannelGrant)csbk;
|
DMRChannel channel = channelGrant.getChannel();
|
||||||
DMRChannel channel = dataGrant.getChannel();
|
|
||||||
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
@@ -778,90 +813,13 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||||
|
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
|
||||||
{
|
|
||||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
|
||||||
new IdentifierCollection(csbk.getIdentifiers()));
|
|
||||||
mDetectedCallEventsMap.put(channel, event);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Update the ending timestamp for the event and rebroadcast
|
|
||||||
event.end(csbk.getTimestamp());
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
else
|
||||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
|
||||||
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
|
||||||
if(csbk instanceof ChannelGrant)
|
|
||||||
{
|
{
|
||||||
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
mLog.error("Unrecognized DMR channel grant CSBK ignored: " + csbk.getClass());
|
||||||
DMRChannel channel = tgGrant.getChannel();
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
|
||||||
{
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
|
||||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
|
||||||
{
|
|
||||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
|
||||||
new IdentifierCollection(csbk.getIdentifiers()));
|
|
||||||
mDetectedCallEventsMap.put(channel, event);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Update the ending timestamp for the event and rebroadcast
|
|
||||||
event.end(csbk.getTimestamp());
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
|
|
||||||
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
|
||||||
if(csbk instanceof ChannelGrant)
|
|
||||||
{
|
|
||||||
ChannelGrant channelGrant = (ChannelGrant)csbk;
|
|
||||||
DMRChannel channel = channelGrant.getChannel();
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
|
||||||
{
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
|
||||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
|
||||||
{
|
|
||||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel,
|
|
||||||
new IdentifierCollection(csbk.getIdentifiers()));
|
|
||||||
mDetectedCallEventsMap.put(channel, event);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Update the ending timestamp for the event and rebroadcast
|
|
||||||
event.end(csbk.getTimestamp());
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CAPMAX_ALOHA:
|
case MOTOROLA_CAPMAX_ALOHA:
|
||||||
@@ -877,42 +835,113 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT:
|
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||||
if(csbk instanceof ConnectPlusDataChannelGrant)
|
if(csbk instanceof CapacityMaxOpenModeVoiceChannelUpdate update)
|
||||||
|
{
|
||||||
|
if(hasTrafficChannelManager())
|
||||||
|
{
|
||||||
|
if(update.hasTimeslot1())
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
mic.remove(IdentifierClass.USER);
|
||||||
|
mic.update(update.getChannelTS1());
|
||||||
|
mic.update(update.getTalkgroupTS1());
|
||||||
|
mTrafficChannelManager.processChannelGrant(update.getChannelTS1(), mic, csbk.getOpcode(),
|
||||||
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
|
}
|
||||||
|
if(update.hasTimeslot2())
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
mic.remove(IdentifierClass.USER);
|
||||||
|
mic.update(update.getChannelTS2());
|
||||||
|
mic.update(update.getTalkgroupTS2());
|
||||||
|
mTrafficChannelManager.processChannelGrant(update.getChannelTS2(), mic, csbk.getOpcode(),
|
||||||
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||||
|
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
|
break;
|
||||||
|
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE:
|
||||||
|
if(csbk instanceof CapacityMaxAdvantageModeVoiceChannelUpdate update)
|
||||||
|
{
|
||||||
|
if(hasTrafficChannelManager())
|
||||||
|
{
|
||||||
|
if(update.hasChannel1Timeslot1())
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
mic.remove(IdentifierClass.USER);
|
||||||
|
mic.update(update.getChannel1TS1());
|
||||||
|
mic.update(update.getTalkgroupCH1TS1());
|
||||||
|
mTrafficChannelManager.processChannelGrant(update.getChannel1TS1(), mic, csbk.getOpcode(),
|
||||||
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(update.hasChannel1Timeslot2())
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
mic.remove(IdentifierClass.USER);
|
||||||
|
mic.update(update.getChannel1TS2());
|
||||||
|
mic.update(update.getTalkgroupCH1TS2());
|
||||||
|
mTrafficChannelManager.processChannelGrant(update.getChannel1TS2(), mic, csbk.getOpcode(),
|
||||||
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(update.hasChannel2Timeslot1())
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
mic.remove(IdentifierClass.USER);
|
||||||
|
mic.update(update.getChannel2TS1());
|
||||||
|
mic.update(update.getTalkgroupCH2TS1());
|
||||||
|
mTrafficChannelManager.processChannelGrant(update.getChannel2TS1(), mic, csbk.getOpcode(),
|
||||||
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(update.hasChannel2Timeslot2())
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
mic.remove(IdentifierClass.USER);
|
||||||
|
mic.update(update.getChannel2TS2());
|
||||||
|
mic.update(update.getTalkgroupCH2TS2());
|
||||||
|
mTrafficChannelManager.processChannelGrant(update.getChannel2TS2(), mic, csbk.getOpcode(),
|
||||||
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||||
|
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
|
break;
|
||||||
|
case MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT:
|
||||||
|
if(csbk instanceof ConnectPlusDataChannelGrant cpdcg)
|
||||||
{
|
{
|
||||||
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
|
|
||||||
DMRChannel channel = cpdcg.getChannel();
|
DMRChannel channel = cpdcg.getChannel();
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||||
csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||||
|
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
|
||||||
{
|
|
||||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
|
||||||
new IdentifierCollection(csbk.getIdentifiers()));
|
|
||||||
mDetectedCallEventsMap.put(channel, event);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Update the ending timestamp for the event and rebroadcast
|
|
||||||
event.end(csbk.getTimestamp());
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
||||||
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.details("Registration Request")
|
.details("Registration Request")
|
||||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
@@ -922,6 +951,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
||||||
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.details("Registration Response")
|
.details("Registration Response")
|
||||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
@@ -930,41 +960,27 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
|
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
|
||||||
if(csbk instanceof ConnectPlusVoiceChannelUser)
|
if(csbk instanceof ConnectPlusVoiceChannelUser cpvcu)
|
||||||
{
|
{
|
||||||
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
|
||||||
DMRChannel channel = cpvcu.getChannel();
|
DMRChannel channel = cpvcu.getChannel();
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||||
csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel);
|
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||||
|
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||||
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
|
||||||
{
|
|
||||||
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
|
||||||
new IdentifierCollection(csbk.getIdentifiers()));
|
|
||||||
mDetectedCallEventsMap.put(channel, detectedEvent);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Update the ending timestamp for the event and rebroadcast
|
|
||||||
detectedEvent.end(csbk.getTimestamp());
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(detectedEvent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
||||||
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.details("TALKGROUP AFFILIATION")
|
.details("TALKGROUP AFFILIATION")
|
||||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
@@ -978,18 +994,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel,
|
|
||||||
IdentifierCollection identifierCollection) {
|
|
||||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
|
||||||
.channel(channel)
|
|
||||||
.details(csbk.getOpcode().getLabel())
|
|
||||||
.identifiers(identifierCollection)
|
|
||||||
.timeslot(getTimeslot())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
|
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
|
||||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(details)
|
.details(details)
|
||||||
@@ -1010,82 +1017,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
return mic;
|
return mic;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the event is a stale event, meaning that the event is null, or the event start exceeds the max
|
|
||||||
* valid call duration threshold, or if the event identifiers don't match the current identifiers.
|
|
||||||
*
|
|
||||||
* @param event to check for staleness
|
|
||||||
* @param timestamp to check the event against
|
|
||||||
* @param currentIdentifiers to compare against the event
|
|
||||||
* @return true if the event is stale.
|
|
||||||
*/
|
|
||||||
private boolean isStale(DecodeEvent event, long timestamp, List<Identifier> currentIdentifiers)
|
|
||||||
{
|
|
||||||
if(event == null || (timestamp - event.getTimeStart() > MAX_VALID_CALL_DURATION_MS))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !isSameCall(event.getIdentifierCollection(), currentIdentifiers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the TO/FROM identifiers in the identifier collection match the TO/FROM identifiers in the list
|
|
||||||
* of identifiers.
|
|
||||||
* @param identifierCollection containing TO/FROM identifiers
|
|
||||||
* @param identifiers containing TO/FROM identifiers
|
|
||||||
* @return true if the TO/FROM identifiers in each collection match
|
|
||||||
*/
|
|
||||||
private boolean isSameCall(IdentifierCollection identifierCollection, List<Identifier> identifiers)
|
|
||||||
{
|
|
||||||
IntegerIdentifier to = null;
|
|
||||||
IntegerIdentifier from = null;
|
|
||||||
|
|
||||||
for(Identifier identifier: identifierCollection.getIdentifiers(IdentifierClass.USER, Role.TO))
|
|
||||||
{
|
|
||||||
if(identifier instanceof IntegerIdentifier)
|
|
||||||
{
|
|
||||||
to = (IntegerIdentifier)identifier;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(Identifier identifier: identifierCollection.getIdentifiers(IdentifierClass.USER, Role.FROM))
|
|
||||||
{
|
|
||||||
if(identifier instanceof IntegerIdentifier)
|
|
||||||
{
|
|
||||||
from = (IntegerIdentifier)identifier;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(to == null || from == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean toMatch = false;
|
|
||||||
boolean fromMatch = false;
|
|
||||||
|
|
||||||
for(Identifier identifier: identifiers)
|
|
||||||
{
|
|
||||||
if(identifier.getRole() == Role.TO &&
|
|
||||||
identifier instanceof IntegerIdentifier &&
|
|
||||||
((IntegerIdentifier)identifier).getValue() == to.getValue())
|
|
||||||
{
|
|
||||||
toMatch = true;
|
|
||||||
}
|
|
||||||
else if(identifier.getRole() == Role.FROM &&
|
|
||||||
identifier instanceof IntegerIdentifier &&
|
|
||||||
((IntegerIdentifier)identifier).getValue() == from.getValue())
|
|
||||||
{
|
|
||||||
fromMatch = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toMatch & fromMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes Link Control Messages
|
* Processes Link Control Messages
|
||||||
* @param isTerminator set to true when the link control is carried by a terminator
|
* @param isTerminator set to true when the link control is carried by a terminator
|
||||||
@@ -1100,6 +1031,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
if(mCurrentCallEvent != null)
|
if(mCurrentCallEvent != null)
|
||||||
{
|
{
|
||||||
mCurrentCallEvent.setDetails(ep.getDetails());
|
mCurrentCallEvent.setDetails(ep.getDetails());
|
||||||
|
broadcast(mCurrentCallEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1114,6 +1046,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp());
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
getIdentifierCollection().update(cpgvcu.getTalkgroup());
|
getIdentifierCollection().update(cpgvcu.getTalkgroup());
|
||||||
}
|
}
|
||||||
@@ -1131,6 +1064,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp());
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
getIdentifierCollection().update(cpgvcu.getTalkgroup());
|
getIdentifierCollection().update(cpgvcu.getTalkgroup());
|
||||||
}
|
}
|
||||||
@@ -1148,6 +1082,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp());
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
getIdentifierCollection().update(cmvcu.getTalkgroup());
|
getIdentifierCollection().update(cmvcu.getTalkgroup());
|
||||||
}
|
}
|
||||||
@@ -1160,6 +1095,28 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case FULL_CAPACITY_MAX_TALKER_ALIAS:
|
||||||
|
if(message instanceof CapacityMaxTalkerAlias alias)
|
||||||
|
{
|
||||||
|
getIdentifierCollection().update(alias.getTalkerAliasIdentifier());
|
||||||
|
|
||||||
|
if(mCurrentCallEvent != null)
|
||||||
|
{
|
||||||
|
broadcast(mCurrentCallEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
|
||||||
|
if(message instanceof CapacityMaxTalkerAliasContinuation alias)
|
||||||
|
{
|
||||||
|
getIdentifierCollection().update(alias.getTalkerAliasIdentifier());
|
||||||
|
|
||||||
|
if(mCurrentCallEvent != null)
|
||||||
|
{
|
||||||
|
broadcast(mCurrentCallEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
|
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
|
||||||
if(message instanceof CapacityPlusWideAreaVoiceChannelUser)
|
if(message instanceof CapacityPlusWideAreaVoiceChannelUser)
|
||||||
{
|
{
|
||||||
@@ -1169,6 +1126,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp());
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
getIdentifierCollection().update(cpuo4.getTalkgroup());
|
getIdentifierCollection().update(cpuo4.getTalkgroup());
|
||||||
}
|
}
|
||||||
@@ -1185,6 +1143,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp());
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
getIdentifierCollection().update(hgvcu.getTalkgroup());
|
getIdentifierCollection().update(hgvcu.getTalkgroup());
|
||||||
}
|
}
|
||||||
@@ -1209,6 +1168,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp());
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
getIdentifierCollection().update(huuvcu.getTargetRadio());
|
getIdentifierCollection().update(huuvcu.getTargetRadio());
|
||||||
}
|
}
|
||||||
@@ -1228,6 +1188,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp());
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
getIdentifierCollection().update(gvcu.getTalkgroup());
|
getIdentifierCollection().update(gvcu.getTalkgroup());
|
||||||
}
|
}
|
||||||
@@ -1247,6 +1208,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
|
closeCurrentCallEvent(message.getTimestamp());
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
getIdentifierCollection().update(uuvcu.getTargetRadio());
|
getIdentifierCollection().update(uuvcu.getTargetRadio());
|
||||||
}
|
}
|
||||||
@@ -1336,8 +1298,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
.identifiers(getIdentifierCollection().copyOf())
|
.identifiers(getIdentifierCollection().copyOf())
|
||||||
.timeslot(getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
broadcast(mCurrentCallEvent);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1348,9 +1308,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
|
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
|
||||||
mCurrentCallEvent.end(timestamp);
|
mCurrentCallEvent.end(timestamp);
|
||||||
broadcast(mCurrentCallEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
broadcast(mCurrentCallEvent);
|
||||||
|
|
||||||
if(type == DecodeEventType.CALL_GROUP_ENCRYPTED || type == DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED)
|
if(type == DecodeEventType.CALL_GROUP_ENCRYPTED || type == DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED)
|
||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, event, State.ENCRYPTED, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, event, State.ENCRYPTED, getTimeslot()));
|
||||||
@@ -1403,7 +1364,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
mCurrentFrequency = event.getFrequency();
|
mCurrentFrequency = event.getFrequency();
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
mTrafficChannelManager.setCurrentControlFrequency(mCurrentFrequency);
|
mTrafficChannelManager.setCurrentControlFrequency(mCurrentFrequency, mChannel);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
private static final String BRAND_TIER_3_TRUNKING = "Tier III Trunking";
|
private static final String BRAND_TIER_3_TRUNKING = "Tier III Trunking";
|
||||||
private static final String BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING = "Capacity Max Tier III Trunking";
|
private static final String BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING = "Capacity Max Tier III Trunking";
|
||||||
private static final String BRAND_HYTERA_TIER_3_TRUNKING = "Hytera Tier III Trunking";
|
private static final String BRAND_HYTERA_TIER_3_TRUNKING = "Hytera Tier III Trunking";
|
||||||
|
private static final String MODE_CAPACITY_MAX_OPEN_SYSTEM = "Open System";
|
||||||
|
private static final String MODE_CAPACITY_MAX_ADVANTAGE = "Advantage";
|
||||||
|
|
||||||
private List<SiteIdentifier> mNeighborSites = new ArrayList<>();
|
private List<SiteIdentifier> mNeighborSites = new ArrayList<>();
|
||||||
private Map<Integer,AdjacentSiteInformation> mTier3NeighborSites = new HashMap<>();
|
private Map<Integer,AdjacentSiteInformation> mTier3NeighborSites = new HashMap<>();
|
||||||
@@ -67,6 +69,7 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
private DMRSite mDMRSite;
|
private DMRSite mDMRSite;
|
||||||
private Model mTier3Model;
|
private Model mTier3Model;
|
||||||
private String mBrand;
|
private String mBrand;
|
||||||
|
private String mMode;
|
||||||
private Integer mColorCode;
|
private Integer mColorCode;
|
||||||
private DMRChannel mCurrentChannel;
|
private DMRChannel mCurrentChannel;
|
||||||
private Channel mChannel;
|
private Channel mChannel;
|
||||||
@@ -94,13 +97,13 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
*/
|
*/
|
||||||
public void process(DMRMessage message)
|
public void process(DMRMessage message)
|
||||||
{
|
{
|
||||||
if(message instanceof CSBKMessage)
|
if(message instanceof CSBKMessage csbk)
|
||||||
{
|
{
|
||||||
process((CSBKMessage)message);
|
process(csbk);
|
||||||
}
|
}
|
||||||
else if(message instanceof LCMessage)
|
else if(message instanceof LCMessage lc)
|
||||||
{
|
{
|
||||||
process((LCMessage)message);
|
process(lc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +175,12 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case STANDARD_ANNOUNCEMENT:
|
||||||
|
if(csbk instanceof AdjacentSiteInformation neighbor)
|
||||||
|
{
|
||||||
|
mTier3NeighborSites.put(neighbor.getNeighborSystemIdentityCode().getSite().getValue(), neighbor);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case HYTERA_08_ANNOUNCEMENT:
|
case HYTERA_08_ANNOUNCEMENT:
|
||||||
case HYTERA_68_ANNOUNCEMENT:
|
case HYTERA_68_ANNOUNCEMENT:
|
||||||
if(csbk instanceof HyteraAnnouncement)
|
if(csbk instanceof HyteraAnnouncement)
|
||||||
@@ -240,6 +249,12 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE:
|
||||||
|
mMode = MODE_CAPACITY_MAX_ADVANTAGE;
|
||||||
|
break;
|
||||||
|
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||||
|
mMode = MODE_CAPACITY_MAX_OPEN_SYSTEM;
|
||||||
|
break;
|
||||||
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
|
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
|
||||||
if(mNeighborSites.isEmpty() && csbk instanceof ConnectPlusNeighborReport)
|
if(mNeighborSites.isEmpty() && csbk instanceof ConnectPlusNeighborReport)
|
||||||
{
|
{
|
||||||
@@ -297,6 +312,11 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
//DMR System Brand Name
|
//DMR System Brand Name
|
||||||
sb.append("\n\nBrand:").append((mBrand == null ? BRAND_STANDARD : mBrand));
|
sb.append("\n\nBrand:").append((mBrand == null ? BRAND_STANDARD : mBrand));
|
||||||
|
|
||||||
|
if(mMode != null)
|
||||||
|
{
|
||||||
|
sb.append("\nConfigured Mode:").append(mMode);
|
||||||
|
}
|
||||||
|
|
||||||
if(mDMRNetwork != null)
|
if(mDMRNetwork != null)
|
||||||
{
|
{
|
||||||
sb.append("\nNetwork:").append(mDMRNetwork);
|
sb.append("\nNetwork:").append(mDMRNetwork);
|
||||||
@@ -347,6 +367,11 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
{
|
{
|
||||||
sb.append("\tNeighbor: ").append(neighbor).append("\n");
|
sb.append("\tNeighbor: ").append(neighbor).append("\n");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!mTier3NeighborSites.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append("\nTier III Neighbor Sites\n");
|
||||||
|
|
||||||
for(Map.Entry<Integer,AdjacentSiteInformation> neighbor: mTier3NeighborSites.entrySet())
|
for(Map.Entry<Integer,AdjacentSiteInformation> neighbor: mTier3NeighborSites.entrySet())
|
||||||
{
|
{
|
||||||
@@ -354,7 +379,16 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
sb.append("\tNeighbor: ").append("Network:").append(site.getNeighborSystemIdentityCode().getNetwork());
|
sb.append("\tNeighbor: ").append("Network:").append(site.getNeighborSystemIdentityCode().getNetwork());
|
||||||
sb.append(" Site:").append(site.getNeighborSystemIdentityCode().getSite());
|
sb.append(" Site:").append(site.getNeighborSystemIdentityCode().getSite());
|
||||||
sb.append(" ").append(site.getNeighborSystemIdentityCode().getModel());
|
sb.append(" ").append(site.getNeighborSystemIdentityCode().getModel());
|
||||||
sb.append(" ").append(site.getNeighborChannel()).append("\n");
|
|
||||||
|
DMRChannel channel = site.getNeighborChannel();
|
||||||
|
sb.append(" ").append(channel);
|
||||||
|
|
||||||
|
if(channel.getDownlinkFrequency() > 0)
|
||||||
|
{
|
||||||
|
sb.append(" ").append(channel.getDownlinkFrequency());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import io.github.dsheirer.message.MessageHistoryRequest;
|
|||||||
import io.github.dsheirer.message.MessageHistoryResponse;
|
import io.github.dsheirer.message.MessageHistoryResponse;
|
||||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.event.DMRChannelGrantEvent;
|
import io.github.dsheirer.module.decode.dmr.event.DMRDecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
@@ -55,6 +55,7 @@ import io.github.dsheirer.source.config.SourceConfigTuner;
|
|||||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.DisableChannelRotationMonitorRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.DisableChannelRotationMonitorRequest;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -63,6 +64,8 @@ import java.util.Objects;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -83,36 +86,32 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* This manager monitors channel events watching for events related to managed traffic channels.
|
* This manager monitors channel events watching for events related to managed traffic channels.
|
||||||
*/
|
*/
|
||||||
public class DMRTrafficChannelManager extends TrafficChannelManager implements IDecodeEventProvider, IChannelEventListener,
|
public class DMRTrafficChannelManager extends TrafficChannelManager implements IChannelEventListener,
|
||||||
IChannelEventProvider
|
IChannelEventProvider, IDecodeEventProvider
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DMRTrafficChannelManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DMRTrafficChannelManager.class);
|
||||||
public static final String CHANNEL_START_REJECTED = " REJECTED - NO TUNER";
|
public static final String CHANNEL_START_REJECTED = " REJECTED - NO TUNER";
|
||||||
public static final String DATA_CALL_IGNORED = "DATA CALL IGNORED";
|
public static final String DATA_CALL_IGNORED = "DATA CALL IGNORED";
|
||||||
public static final String IGNORED = " - Ignored";
|
|
||||||
public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED";
|
public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED";
|
||||||
public static final String NO_FREQUENCY = "NO FREQUENCY - CHECK LSN CHANNEL MAP";
|
public static final String NO_FREQUENCY = "NO FREQUENCY - CHECK PLAYLIST CHANNEL CONFIG LSN CHANNEL MAP";
|
||||||
public static final long EVENT_TIME_STALE_THRESHOLD = 5000; //5 seconds
|
public static final long EVENT_TIME_STALE_THRESHOLD = 5000; //5 seconds
|
||||||
|
|
||||||
private Queue<Channel> mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>();
|
private Queue<Channel> mAvailableTrafficChannels = new ConcurrentLinkedQueue<>();
|
||||||
private List<Channel> mManagedTrafficChannels;
|
private List<Channel> mAllocatedTrafficChannels;
|
||||||
|
private Map<Long,Channel> mAllocatedChannelFrequencyMap = new ConcurrentHashMap<>();
|
||||||
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
|
private ReentrantLock mLock = new ReentrantLock();
|
||||||
private Map<DMRChannel,DMRChannelGrantEvent> mChannelGrantEventMap = new ConcurrentHashMap<>();
|
private Map<Long,IDecodeEvent> mCallEventsTS1 = new ConcurrentHashMap<>();
|
||||||
|
private Map<Long,IDecodeEvent> mCallEventsTS2 = new ConcurrentHashMap<>();
|
||||||
private Listener<ChannelEvent> mChannelEventListener;
|
private Listener<ChannelEvent> mChannelEventListener;
|
||||||
private Listener<IDecodeEvent> mDecodeEventListener;
|
private Listener<IDecodeEvent> mDecodeEventListener;
|
||||||
|
|
||||||
private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor();
|
private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor();
|
||||||
private Channel mParentChannel;
|
private Channel mParentChannel;
|
||||||
|
private boolean mIgnoreDataCalls;
|
||||||
|
|
||||||
//Used as temporary storage for message and decode event history during Cap+ REST channel rotation
|
//Used as temporary storage for message and decode event history during Cap+ REST channel rotation
|
||||||
private DecodeEventHistory mTransientDecodeEventHistory;
|
private DecodeEventHistory mTransientDecodeEventHistory;
|
||||||
private List<IMessage> mTransientMessageHistory;
|
private List<IMessage> mTransientMessageHistory;
|
||||||
|
|
||||||
private boolean mIgnoreDataCalls;
|
|
||||||
private long mCurrentControlFrequency;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitors call events and allocates traffic decoder channels in response
|
* Monitors call events and allocates traffic decoder channels in response
|
||||||
* to traffic channel allocation call events. Manages a pool of reusable
|
* to traffic channel allocation call events. Manages a pool of reusable
|
||||||
@@ -136,10 +135,11 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
* Sets the current parent control channel frequency so that channel grants for the current frequency do not
|
* Sets the current parent control channel frequency so that channel grants for the current frequency do not
|
||||||
* produce an additional traffic channel allocation.
|
* produce an additional traffic channel allocation.
|
||||||
* @param currentControlFrequency for current control channel.
|
* @param currentControlFrequency for current control channel.
|
||||||
|
* @param channel for the current control channel
|
||||||
*/
|
*/
|
||||||
public void setCurrentControlFrequency(long currentControlFrequency)
|
public void setCurrentControlFrequency(long currentControlFrequency, Channel channel)
|
||||||
{
|
{
|
||||||
mCurrentControlFrequency = currentControlFrequency;
|
mAllocatedChannelFrequencyMap.put(currentControlFrequency, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,7 +149,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
*/
|
*/
|
||||||
private void createTrafficChannels()
|
private void createTrafficChannels()
|
||||||
{
|
{
|
||||||
if(mManagedTrafficChannels == null)
|
if(mAllocatedTrafficChannels == null)
|
||||||
{
|
{
|
||||||
DecodeConfiguration decodeConfiguration = mParentChannel.getDecodeConfiguration();
|
DecodeConfiguration decodeConfiguration = mParentChannel.getDecodeConfiguration();
|
||||||
List<Channel> trafficChannelList = new ArrayList<>();
|
List<Channel> trafficChannelList = new ArrayList<>();
|
||||||
@@ -176,8 +176,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mAvailableTrafficChannelQueue.addAll(trafficChannelList);
|
mAvailableTrafficChannels.addAll(trafficChannelList);
|
||||||
mManagedTrafficChannels = Collections.unmodifiableList(trafficChannelList);
|
mAllocatedTrafficChannels = Collections.unmodifiableList(trafficChannelList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,85 +191,92 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
public void convertToTrafficChannel(Channel channel, long currentFrequency, IChannelDescriptor restChannel,
|
public void convertToTrafficChannel(Channel channel, long currentFrequency, IChannelDescriptor restChannel,
|
||||||
DMRNetworkConfigurationMonitor networkConfigurationMonitor)
|
DMRNetworkConfigurationMonitor networkConfigurationMonitor)
|
||||||
{
|
{
|
||||||
//Only do the conversion of the original channel has multiple frequencies defined and the rest channel is
|
mLock.lock();
|
||||||
//one of those frequencies
|
|
||||||
if(restChannel.getDownlinkFrequency() > 0 &&
|
try
|
||||||
!mAllocatedTrafficChannelFrequencyMap.containsKey(restChannel.getDownlinkFrequency()) &&
|
|
||||||
channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
|
||||||
{
|
{
|
||||||
SourceConfigTunerMultipleFrequency originalSourceConfig = (SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration();
|
long rest = restChannel.getDownlinkFrequency();
|
||||||
|
|
||||||
//Add the rest channel to the list of frequencies in the source configuration
|
//Only do the conversion of the original channel has multiple frequencies defined and the rest channel is
|
||||||
if(!originalSourceConfig.getFrequencies().contains(restChannel.getDownlinkFrequency()))
|
//one of those frequencies
|
||||||
|
if(rest > 0 && !mAllocatedChannelFrequencyMap.containsKey(rest) &&
|
||||||
|
channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
||||||
{
|
{
|
||||||
originalSourceConfig.addFrequency(restChannel.getDownlinkFrequency());
|
SourceConfigTunerMultipleFrequency originalSourceConfig = (SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration();
|
||||||
|
|
||||||
//Note: the channel configuration modification is not persisted from here, to avoid unnecessary coupling
|
//Add the rest channel to the list of frequencies in the source configuration
|
||||||
//to the channel model. The configuration will simply be adjusted at runtime.
|
if(!originalSourceConfig.getFrequencies().contains(restChannel.getDownlinkFrequency()))
|
||||||
}
|
{
|
||||||
|
originalSourceConfig.addFrequency(restChannel.getDownlinkFrequency());
|
||||||
//Convert current channel to a traffic channel if one is available
|
}
|
||||||
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
|
|
||||||
|
//Convert current channel to a traffic channel if one is available
|
||||||
if(trafficChannel != null && restChannel.getDownlinkFrequency() > 0)
|
Channel trafficChannel = mAvailableTrafficChannels.poll();
|
||||||
{
|
|
||||||
//Disable the channel rotation manager when we have multiple frequencies defined
|
if(trafficChannel != null)
|
||||||
getInterModuleEventBus().post(new DisableChannelRotationMonitorRequest());
|
{
|
||||||
|
//Disable the channel rotation manager when we have multiple frequencies defined
|
||||||
//Set the frequency for the traffic channel configuration
|
getInterModuleEventBus().post(new DisableChannelRotationMonitorRequest());
|
||||||
SourceConfigTuner trafficSourceConfig = new SourceConfigTuner();
|
|
||||||
trafficSourceConfig.setFrequency(currentFrequency);
|
//Set the frequency for the traffic channel configuration
|
||||||
trafficChannel.setSourceConfiguration(trafficSourceConfig);
|
SourceConfigTuner trafficSourceConfig = new SourceConfigTuner();
|
||||||
|
trafficSourceConfig.setFrequency(currentFrequency);
|
||||||
//Post a request for message and decode event history to transfer to the new REST channel. This has
|
trafficChannel.setSourceConfiguration(trafficSourceConfig);
|
||||||
// to be posted before the channel conversion request
|
|
||||||
getInterModuleEventBus().post(new DecodeEventHistoryRequest());
|
//Post a request for message and decode event history to transfer to the new REST channel. This has
|
||||||
getInterModuleEventBus().post(new MessageHistoryRequest());
|
// to be posted before the channel conversion request
|
||||||
|
getInterModuleEventBus().post(new DecodeEventHistoryRequest());
|
||||||
//Dispatch a request to convert this processing chain to the traffic channel. This will cause the
|
getInterModuleEventBus().post(new MessageHistoryRequest());
|
||||||
//processing chain to convert to a traffic channel and notify all of the modules, which will in-turn
|
|
||||||
//cause the decoder states (2 timeslots) to dereference this manager so that the existing channel can
|
//Dispatch a request to convert this processing chain to the traffic channel. This will cause the
|
||||||
//no longer allocate traffic channels.
|
//processing chain to convert to a traffic channel and notify all of the modules, which will in-turn
|
||||||
getInterModuleEventBus().post(new ChannelConversionRequest(channel, trafficChannel));
|
//cause the decoder states (2 timeslots) to dereference this manager so that the existing channel can
|
||||||
|
//no longer allocate traffic channels.
|
||||||
mAllocatedTrafficChannelFrequencyMap.put(currentFrequency, trafficChannel);
|
getInterModuleEventBus().post(new ChannelConversionRequest(channel, trafficChannel));
|
||||||
|
|
||||||
//Set the preferred frequency to use when restarting the original channel
|
mAllocatedChannelFrequencyMap.put(currentFrequency, trafficChannel);
|
||||||
originalSourceConfig.setPreferredFrequency(restChannel.getDownlinkFrequency());
|
|
||||||
|
//Set the preferred frequency to use when restarting the original channel
|
||||||
//Dispatch request to persistently start the original channel with the rest channel frequency and reuse
|
originalSourceConfig.setPreferredFrequency(rest);
|
||||||
//this traffic channel manager in the new processing chain.
|
|
||||||
ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, this);
|
//Dispatch request to persistently start the original channel with the rest channel frequency and reuse
|
||||||
request.setPersistentAttempt(true);
|
//this traffic channel manager in the new processing chain.
|
||||||
request.setChildDecodeEventHistory(mTransientDecodeEventHistory);
|
ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, this);
|
||||||
|
request.setPersistentAttempt(true);
|
||||||
//If we received an event history response, add it to the request as preload data content
|
request.setChildDecodeEventHistory(mTransientDecodeEventHistory);
|
||||||
if(mTransientDecodeEventHistory != null)
|
|
||||||
{
|
//If we received an event history response, add it to the request as preload data content
|
||||||
DecodeEventHistoryPreloadData eventHistory =
|
if(mTransientDecodeEventHistory != null)
|
||||||
new DecodeEventHistoryPreloadData(mTransientDecodeEventHistory.getItems());
|
{
|
||||||
request.addPreloadDataContent(eventHistory);
|
DecodeEventHistoryPreloadData eventHistory =
|
||||||
|
new DecodeEventHistoryPreloadData(mTransientDecodeEventHistory.getItems());
|
||||||
mTransientDecodeEventHistory = null;
|
request.addPreloadDataContent(eventHistory);
|
||||||
}
|
|
||||||
|
mTransientDecodeEventHistory = null;
|
||||||
//If we received a message history response, add it to the request as preload data content
|
}
|
||||||
if(mTransientMessageHistory != null)
|
|
||||||
{
|
//If we received a message history response, add it to the request as preload data content
|
||||||
MessageHistoryPreloadData messageHistory = new MessageHistoryPreloadData(mTransientMessageHistory);
|
if(mTransientMessageHistory != null)
|
||||||
request.addPreloadDataContent(messageHistory);
|
{
|
||||||
mTransientMessageHistory = null;
|
MessageHistoryPreloadData messageHistory = new MessageHistoryPreloadData(mTransientMessageHistory);
|
||||||
}
|
request.addPreloadDataContent(messageHistory);
|
||||||
|
mTransientMessageHistory = null;
|
||||||
//Add the DMR network configuration monitor as preload data
|
}
|
||||||
if(networkConfigurationMonitor != null)
|
|
||||||
{
|
//Add the DMR network configuration monitor as preload data
|
||||||
request.addPreloadDataContent(new DMRNetworkConfigurationPreloadData(networkConfigurationMonitor));
|
if(networkConfigurationMonitor != null)
|
||||||
}
|
{
|
||||||
|
request.addPreloadDataContent(new DMRNetworkConfigurationPreloadData(networkConfigurationMonitor));
|
||||||
getInterModuleEventBus().post(request);
|
}
|
||||||
|
|
||||||
|
getInterModuleEventBus().post(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -301,7 +308,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
/**
|
/**
|
||||||
* Broadcasts an initial or update decode event to any registered listener.
|
* Broadcasts an initial or update decode event to any registered listener.
|
||||||
*/
|
*/
|
||||||
public void broadcast(DecodeEvent decodeEvent)
|
public void broadcast(IDecodeEvent decodeEvent)
|
||||||
{
|
{
|
||||||
if(mDecodeEventListener != null)
|
if(mDecodeEventListener != null)
|
||||||
{
|
{
|
||||||
@@ -309,119 +316,159 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processEndChannelGrant()
|
/**
|
||||||
|
* Receive decode events/updates from traffic channels to capture in the parent channel's decode event history. We
|
||||||
|
* do this by rebroadcasting the event to the parent control channel's processing chain so that the event history
|
||||||
|
* can receive the event or update.
|
||||||
|
* @param trafficChannelEvent to receive and rebroadcast.
|
||||||
|
*/
|
||||||
|
public void receiveTrafficChannelEvent(IDecodeEvent trafficChannelEvent)
|
||||||
{
|
{
|
||||||
//TODO: process the CSBK opcode that indicates traffic channel teardown
|
broadcast(trafficChannelEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes channel grants to allocate traffic channels and track overall channel usage. Generates
|
* Processes channel grants to allocate traffic channels and track overall channel usage. Generates
|
||||||
* decode events for each new channel that is allocated.
|
* decode events for each new channel that is allocated.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
|
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
|
||||||
Opcode opcode, long timestamp, boolean encrypted)
|
Opcode opcode, long timestamp, boolean encrypted)
|
||||||
{
|
{
|
||||||
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
mLock.lock();
|
||||||
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
|
||||||
|
|
||||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
try
|
||||||
{
|
{
|
||||||
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
boolean allocated = mAllocatedChannelFrequencyMap.containsKey(channel.getDownlinkFrequency());
|
||||||
.channel(channel)
|
|
||||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
|
||||||
.identifiers(identifierCollection)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mChannelGrantEventMap.put(channel, event);
|
if(allocated)
|
||||||
|
|
||||||
}
|
|
||||||
else //Update current event
|
|
||||||
{
|
|
||||||
Identifier from = getIdentifier(identifierCollection, Role.FROM);
|
|
||||||
|
|
||||||
if(from != null)
|
|
||||||
{
|
{
|
||||||
Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM);
|
//Note: if traffic channel is allocated then do nothing & let the channel maintain its own event state
|
||||||
if(currentFrom != null && !Objects.equals(from, currentFrom))
|
|
||||||
{
|
|
||||||
event.end(timestamp);
|
|
||||||
|
|
||||||
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
|
||||||
.channel(channel)
|
|
||||||
.details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
|
||||||
.identifiers(identifierCollection)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mChannelGrantEventMap.put(channel, event);
|
|
||||||
broadcast(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//update the ending timestamp so that the duration value is correctly calculated
|
|
||||||
event.update(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(event);
|
|
||||||
|
|
||||||
//Even though we have an event, the initial or continuation channel grant may have been rejected. Check to see
|
|
||||||
// if there is a traffic channel allocated. If not, allocate one and update the event description.
|
|
||||||
long frequency = channel.getDownlinkFrequency();
|
|
||||||
|
|
||||||
if(frequency == 0)
|
|
||||||
{
|
|
||||||
if(event.getDetails() == null)
|
|
||||||
{
|
|
||||||
event.setDetails(NO_FREQUENCY);
|
|
||||||
}
|
|
||||||
else if(!event.getDetails().endsWith(NO_FREQUENCY))
|
|
||||||
{
|
|
||||||
event.setDetails(event.getDetails() + " - " + NO_FREQUENCY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(frequency != mCurrentControlFrequency && !mAllocatedTrafficChannelFrequencyMap.containsKey(frequency))
|
|
||||||
{
|
|
||||||
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
|
|
||||||
{
|
|
||||||
if(event.getDetails() == null)
|
|
||||||
{
|
|
||||||
event.setDetails(DATA_CALL_IGNORED);
|
|
||||||
}
|
|
||||||
else if(!event.getDetails().endsWith(DATA_CALL_IGNORED))
|
|
||||||
{
|
|
||||||
event.setDetails(event.getDetails() + " - " + DATA_CALL_IGNORED);
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
|
|
||||||
|
|
||||||
if(trafficChannel != null)
|
|
||||||
{
|
|
||||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
|
||||||
sourceConfig.setFrequency(frequency);
|
|
||||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
|
||||||
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel);
|
|
||||||
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel,
|
|
||||||
identifierCollection));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(event.getDetails() == null)
|
long frequency = channel.getDownlinkFrequency();
|
||||||
|
|
||||||
|
//If we don't have a frequency value for the channel, use the channel number as a place holder. We won't
|
||||||
|
//allocate a traffic channel for it, but we can track the related channel grant event.
|
||||||
|
if(frequency == 0)
|
||||||
{
|
{
|
||||||
event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
|
frequency = channel.getChannelNumber();
|
||||||
}
|
}
|
||||||
else if(!event.getDetails().endsWith(MAX_TRAFFIC_CHANNELS_EXCEEDED))
|
|
||||||
|
Map<Long,IDecodeEvent> eventMap = channel.getTimeslot() == 1 ? mCallEventsTS1 : mCallEventsTS2;
|
||||||
|
IDecodeEvent temp = eventMap.get(frequency);
|
||||||
|
DecodeEvent event = (temp != null) ? (DecodeEvent)temp : null;
|
||||||
|
|
||||||
|
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||||
{
|
{
|
||||||
event.setDetails(event.getDetails() + " - " + MAX_TRAFFIC_CHANNELS_EXCEEDED);
|
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||||
|
event = DMRDecodeEvent.builder(decodeEventType, timestamp)
|
||||||
|
.channel(channel)
|
||||||
|
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||||
|
.identifiers(identifierCollection)
|
||||||
|
.timeslot(channel.getTimeslot())
|
||||||
|
.build();
|
||||||
|
eventMap.put(frequency, event);
|
||||||
}
|
}
|
||||||
|
else if(event != null) //Update current event
|
||||||
|
{
|
||||||
|
Identifier from = getIdentifier(identifierCollection, Role.FROM);
|
||||||
|
|
||||||
|
if(from != null)
|
||||||
|
{
|
||||||
|
Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM);
|
||||||
|
if(currentFrom != null && !Objects.equals(from, currentFrom))
|
||||||
|
{
|
||||||
|
event.end(timestamp);
|
||||||
|
|
||||||
|
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||||
|
event = DMRDecodeEvent.builder(decodeEventType, timestamp)
|
||||||
|
.channel(channel)
|
||||||
|
.details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||||
|
.identifiers(identifierCollection)
|
||||||
|
.timeslot(channel.getTimeslot())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
eventMap.put(frequency, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//update the ending timestamp so that the duration value is correctly calculated
|
||||||
|
event.update(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If our channel doesn't have a frequency value, update the event to reflect this.
|
||||||
|
if(channel.getDownlinkFrequency() == 0)
|
||||||
|
{
|
||||||
|
if(event.getDetails() == null)
|
||||||
|
{
|
||||||
|
event.setDetails(NO_FREQUENCY);
|
||||||
|
}
|
||||||
|
else if(!event.getDetails().endsWith(NO_FREQUENCY))
|
||||||
|
{
|
||||||
|
event.setDetails(event.getDetails() + " - " + NO_FREQUENCY);
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
|
||||||
|
{
|
||||||
|
if(event.getDetails() == null)
|
||||||
|
{
|
||||||
|
event.setDetails(DATA_CALL_IGNORED);
|
||||||
|
}
|
||||||
|
else if(!event.getDetails().endsWith(DATA_CALL_IGNORED))
|
||||||
|
{
|
||||||
|
event.setDetails(event.getDetails() + " - " + DATA_CALL_IGNORED);
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel trafficChannel = mAvailableTrafficChannels.poll();
|
||||||
|
|
||||||
|
if(trafficChannel != null)
|
||||||
|
{
|
||||||
|
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||||
|
sourceConfig.setFrequency(frequency);
|
||||||
|
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||||
|
mAllocatedChannelFrequencyMap.put(frequency, trafficChannel);
|
||||||
|
//Preload the channel grant event for the traffic channel to use/maintain
|
||||||
|
if(trafficChannel.getDecodeConfiguration() instanceof DecodeConfigDMR config)
|
||||||
|
{
|
||||||
|
config.setChannelGrantEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel,
|
||||||
|
identifierCollection, this));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(event.getDetails() == null)
|
||||||
|
{
|
||||||
|
event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
|
||||||
|
}
|
||||||
|
else if(!event.getDetails().endsWith(MAX_TRAFFIC_CHANNELS_EXCEEDED))
|
||||||
|
{
|
||||||
|
event.setDetails(event.getDetails() + " - " + MAX_TRAFFIC_CHANNELS_EXCEEDED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
//This shouldn't happen, but we'll log if it ever does.
|
||||||
|
mLog.error("Error while processing channel grant event - traffic channel lock is released", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -436,6 +483,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
{
|
{
|
||||||
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||||
|
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||||
|
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE:
|
||||||
type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
|
type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -540,7 +589,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
* @param currentIdentifiers to compare against the event
|
* @param currentIdentifiers to compare against the event
|
||||||
* @return true if the event is stale.
|
* @return true if the event is stale.
|
||||||
*/
|
*/
|
||||||
private boolean isStale(DecodeEvent event, long timestamp, IdentifierCollection currentIdentifiers)
|
private boolean isStale(IDecodeEvent event, long timestamp, IdentifierCollection currentIdentifiers)
|
||||||
{
|
{
|
||||||
if(event == null)
|
if(event == null)
|
||||||
{
|
{
|
||||||
@@ -609,23 +658,41 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
@Override
|
@Override
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
for(Long frequency: mAllocatedTrafficChannelFrequencyMap.keySet())
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(frequency));
|
for(Long frequency: mAllocatedChannelFrequencyMap.keySet())
|
||||||
|
{
|
||||||
|
getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(frequency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
mAvailableTrafficChannelQueue.clear();
|
mLock.lock();
|
||||||
|
|
||||||
List<Channel> channels = new ArrayList<>(mAllocatedTrafficChannelFrequencyMap.values());
|
try
|
||||||
|
|
||||||
//Issue a disable request for each traffic channel
|
|
||||||
for(Channel channel: channels)
|
|
||||||
{
|
{
|
||||||
broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
|
mAvailableTrafficChannels.clear();
|
||||||
|
|
||||||
|
List<Channel> channels = new ArrayList<>(mAllocatedChannelFrequencyMap.values());
|
||||||
|
|
||||||
|
//Issue a disable request for each traffic channel
|
||||||
|
for(Channel channel: channels)
|
||||||
|
{
|
||||||
|
broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,54 +708,71 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
*/
|
*/
|
||||||
private void removeCallEvents(long frequency)
|
private void removeCallEvents(long frequency)
|
||||||
{
|
{
|
||||||
List<DMRChannel> channelsToRemove = new ArrayList<>();
|
mLock.lock();
|
||||||
|
|
||||||
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
try
|
||||||
{
|
{
|
||||||
if(entry.getKey().getDownlinkFrequency() == frequency)
|
mCallEventsTS1.remove(frequency);
|
||||||
{
|
mCallEventsTS2.remove(frequency);
|
||||||
channelsToRemove.add(entry.getKey());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
for(DMRChannel channel: channelsToRemove)
|
|
||||||
{
|
{
|
||||||
mChannelGrantEventMap.remove(channel);
|
mLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCallEventDetails(long frequency, String detailFragment)
|
/**
|
||||||
|
* Updates any call events related to the frequency to indicate the channel start was rejected.
|
||||||
|
* @param frequency for the channel.
|
||||||
|
*/
|
||||||
|
private void setChannelStartRejected(long frequency)
|
||||||
{
|
{
|
||||||
List<DMRChannel> channelsToUpdate = new ArrayList<>();
|
mLock.lock();
|
||||||
|
|
||||||
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
try
|
||||||
{
|
{
|
||||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
IDecodeEvent event = mCallEventsTS1.get(frequency);
|
||||||
|
|
||||||
|
if(event instanceof DecodeEvent decodeEvent1)
|
||||||
{
|
{
|
||||||
channelsToUpdate.add(entry.getKey());
|
if(decodeEvent1.getDetails() == null)
|
||||||
|
{
|
||||||
|
decodeEvent1.setDetails(CHANNEL_START_REJECTED);
|
||||||
|
}
|
||||||
|
else if(!decodeEvent1.getDetails().endsWith(CHANNEL_START_REJECTED))
|
||||||
|
{
|
||||||
|
decodeEvent1.setDetails(event.getDetails() + "-" + CHANNEL_START_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(decodeEvent1);
|
||||||
|
}
|
||||||
|
|
||||||
|
event = mCallEventsTS2.get(frequency);
|
||||||
|
|
||||||
|
if(event instanceof DecodeEvent decodeEvent2)
|
||||||
|
{
|
||||||
|
if(decodeEvent2.getDetails() == null)
|
||||||
|
{
|
||||||
|
decodeEvent2.setDetails(CHANNEL_START_REJECTED);
|
||||||
|
}
|
||||||
|
else if(!decodeEvent2.getDetails().endsWith(CHANNEL_START_REJECTED))
|
||||||
|
{
|
||||||
|
decodeEvent2.setDetails(decodeEvent2.getDetails() + "-" + CHANNEL_START_REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(decodeEvent2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
for(DMRChannel channel: channelsToUpdate)
|
|
||||||
{
|
{
|
||||||
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
mLock.unlock();
|
||||||
|
|
||||||
if(event != null)
|
|
||||||
{
|
|
||||||
if(event.getDetails() == null)
|
|
||||||
{
|
|
||||||
event.setDetails(detailFragment);
|
|
||||||
}
|
|
||||||
else if(!event.getDetails().endsWith(detailFragment))
|
|
||||||
{
|
|
||||||
event.setDetails(event.getDetails() + detailFragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process traffic channel processing stop or processing start rejected events.
|
||||||
|
* @param channelEvent to process
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public synchronized void receive(ChannelEvent channelEvent)
|
public synchronized void receive(ChannelEvent channelEvent)
|
||||||
{
|
{
|
||||||
@@ -701,55 +785,72 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
case NOTIFICATION_PROCESSING_STOP:
|
case NOTIFICATION_PROCESSING_STOP:
|
||||||
long frequencyToRemove = 0;
|
long frequencyToRemove = 0;
|
||||||
|
|
||||||
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
|
mLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(channel == entry.getValue() && entry.getKey() != null)
|
for(Map.Entry<Long,Channel> entry: mAllocatedChannelFrequencyMap.entrySet())
|
||||||
{
|
{
|
||||||
frequencyToRemove = entry.getKey();
|
if(channel == entry.getValue() && entry.getKey() != null)
|
||||||
removeCallEvents(entry.getKey());
|
{
|
||||||
break;
|
frequencyToRemove = entry.getKey();
|
||||||
|
removeCallEvents(entry.getKey());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(frequencyToRemove > 0)
|
||||||
|
{
|
||||||
|
mAllocatedChannelFrequencyMap.remove(frequencyToRemove);
|
||||||
|
|
||||||
|
//Unlock the frequency in the channel rotation monitor
|
||||||
|
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add the traffic channel back to the queue to be reused
|
||||||
|
if(!mAvailableTrafficChannels.contains(channel))
|
||||||
|
{
|
||||||
|
mAvailableTrafficChannels.add(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
if(frequencyToRemove > 0)
|
|
||||||
{
|
{
|
||||||
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToRemove);
|
mLock.unlock();
|
||||||
|
|
||||||
//Unlock the frequency in the channel rotation monitor
|
|
||||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add the traffic channel back to the queue to be reused
|
|
||||||
if(!mAvailableTrafficChannelQueue.contains(channel))
|
|
||||||
{
|
|
||||||
mAvailableTrafficChannelQueue.add(channel);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NOTIFICATION_PROCESSING_START_REJECTED:
|
case NOTIFICATION_PROCESSING_START_REJECTED:
|
||||||
long frequencyToUpdate = 0;
|
mLock.lock();
|
||||||
|
try
|
||||||
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
|
|
||||||
{
|
{
|
||||||
if(channel == entry.getValue() && entry.getKey() != null)
|
long frequencyToUpdate = 0;
|
||||||
|
|
||||||
|
for(Map.Entry<Long,Channel> entry: mAllocatedChannelFrequencyMap.entrySet())
|
||||||
{
|
{
|
||||||
frequencyToUpdate = entry.getKey();
|
if(channel == entry.getValue() && entry.getKey() != null)
|
||||||
updateCallEventDetails(entry.getKey(), CHANNEL_START_REJECTED);
|
{
|
||||||
break;
|
frequencyToUpdate = entry.getKey();
|
||||||
|
setChannelStartRejected(entry.getKey());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(frequencyToUpdate > 0)
|
||||||
|
{
|
||||||
|
mAllocatedChannelFrequencyMap.remove(frequencyToUpdate);
|
||||||
|
|
||||||
|
//Unlock the frequency in the channel rotation monitor
|
||||||
|
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add the traffic channel back to the queue to be reused
|
||||||
|
if(!mAvailableTrafficChannels.contains(channel))
|
||||||
|
{
|
||||||
|
mAvailableTrafficChannels.add(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
if(frequencyToUpdate > 0)
|
|
||||||
{
|
{
|
||||||
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToUpdate);
|
mLock.unlock();
|
||||||
|
|
||||||
//Unlock the frequency in the channel rotation monitor
|
|
||||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToUpdate));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add the traffic channel back to the queue to be reused
|
|
||||||
if(!mAvailableTrafficChannelQueue.contains(channel))
|
|
||||||
{
|
|
||||||
mAvailableTrafficChannelQueue.add(channel);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
|||||||
import io.github.dsheirer.module.decode.DecoderType;
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -41,6 +42,9 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
|||||||
private boolean mUseCompressedTalkgroups = false;
|
private boolean mUseCompressedTalkgroups = false;
|
||||||
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
|
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private DecodeEvent mChannelGrantEvent;
|
||||||
|
|
||||||
public DecodeConfigDMR()
|
public DecodeConfigDMR()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -191,4 +195,32 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
|||||||
{
|
{
|
||||||
mTimeslotMap.add(timeslotFrequency);
|
mTimeslotMap.add(timeslotFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
/**
|
||||||
|
* Original channel grant event. This is used for trunking systems to pass the original channel grant event to
|
||||||
|
* the decoder state on channel startup.
|
||||||
|
*/
|
||||||
|
public DecodeEvent getChannelGrantEvent()
|
||||||
|
{
|
||||||
|
return mChannelGrantEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the original channel grant event.
|
||||||
|
* @param channelGrantEvent to set on the decoder state.
|
||||||
|
*/
|
||||||
|
public void setChannelGrantEvent(DecodeEvent channelGrantEvent)
|
||||||
|
{
|
||||||
|
mChannelGrantEvent = channelGrantEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
/**
|
||||||
|
* Indicates if this decode config has an original channel grant event to preload.
|
||||||
|
*/
|
||||||
|
public boolean hasChannelGrantEvent()
|
||||||
|
{
|
||||||
|
return mChannelGrantEvent != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,4 +74,12 @@ public class DMRAbsoluteChannel extends DMRChannel
|
|||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DMRChannel getSisterTimeslot()
|
||||||
|
{
|
||||||
|
DMRAbsoluteChannel other = new DMRAbsoluteChannel(getChannelNumber(), getTimeslot() == 1 ? 2 : 1,
|
||||||
|
getDownlinkFrequency(), getUplinkFrequency());
|
||||||
|
return other;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
mTimeslot = timeslot;
|
mTimeslot = timeslot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a channel of the same type, but for the alternate timeslot.
|
||||||
|
* @return sister timeslot channel
|
||||||
|
*/
|
||||||
|
public abstract DMRChannel getSisterTimeslot();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Protocol getProtocol()
|
public Protocol getProtocol()
|
||||||
{
|
{
|
||||||
@@ -160,6 +166,15 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
mTimeslotFrequency = timeslotFrequency;
|
mTimeslotFrequency = timeslotFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TimeslotFrequency for this channel.
|
||||||
|
* @return timeslot frequency instance, or null.
|
||||||
|
*/
|
||||||
|
public TimeslotFrequency getTimeslotFrequency()
|
||||||
|
{
|
||||||
|
return mTimeslotFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -69,4 +69,25 @@ public class DMRLsn extends DMRChannel
|
|||||||
return ((getChannelNumber() - 1) * 2) + getTimeslot();
|
return ((getChannelNumber() - 1) * 2) + getTimeslot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a channel of the same type, but for the alternate timeslot.
|
||||||
|
* @return sister timeslot channel
|
||||||
|
*/
|
||||||
|
public DMRChannel getSisterTimeslot()
|
||||||
|
{
|
||||||
|
int lsn;
|
||||||
|
|
||||||
|
if(getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
lsn = getLsn() + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lsn = getLsn() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DMRLsn other = new DMRLsn(lsn);
|
||||||
|
other.setTimeslotFrequency(getTimeslotFrequency());
|
||||||
|
return other;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,4 +55,12 @@ public class DMRTier3Channel extends DMRChannel
|
|||||||
sb.append(" CHANID:").append(getChannelId());
|
sb.append(" CHANID:").append(getChannelId());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DMRChannel getSisterTimeslot()
|
||||||
|
{
|
||||||
|
DMRTier3Channel other = new DMRTier3Channel(getChannelNumber(), getTimeslot() == 1 ? 2 : 1);
|
||||||
|
other.setTimeslotFrequency(getTimeslotFrequency());
|
||||||
|
return other;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,169 +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 <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
package io.github.dsheirer.module.decode.dmr.event;
|
|
||||||
|
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DMR Channel Grant Event.
|
|
||||||
*
|
|
||||||
* This event class is send traffic channel grant details to the traffic channel manager to allocate a traffic channel.
|
|
||||||
*/
|
|
||||||
public class DMRChannelGrantEvent extends DMRDecodeEvent
|
|
||||||
{
|
|
||||||
private boolean mEncrypted;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
* @param decodeEventType for the event
|
|
||||||
* @param timestamp for the event
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantEvent(DecodeEventType decodeEventType, long timestamp)
|
|
||||||
{
|
|
||||||
super(decodeEventType, timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the channel grant was for an encrypted call
|
|
||||||
*/
|
|
||||||
public boolean isEncrypted()
|
|
||||||
{
|
|
||||||
return mEncrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the encrypte state for the call
|
|
||||||
* @param encrypted
|
|
||||||
*/
|
|
||||||
public void setEncrypted(boolean encrypted)
|
|
||||||
{
|
|
||||||
mEncrypted = encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new decode event builder with the specified start timestamp.
|
|
||||||
* @param timeStart for the event
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(DecodeEventType decodeEventType, long timeStart)
|
|
||||||
{
|
|
||||||
return new DMRChannelGrantDecodeEventBuilder(decodeEventType, timeStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builder pattern for constructing decode events.
|
|
||||||
*/
|
|
||||||
public static class DMRChannelGrantDecodeEventBuilder
|
|
||||||
{
|
|
||||||
protected long mTimeStart;
|
|
||||||
protected long mDuration;
|
|
||||||
protected DecodeEventType mDecodeEventType;
|
|
||||||
protected IdentifierCollection mIdentifierCollection;
|
|
||||||
protected DMRChannel mChannel;
|
|
||||||
protected String mDetails;
|
|
||||||
protected boolean mEncrypted;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a builder instance with the specified start time in milliseconds
|
|
||||||
*
|
|
||||||
* @param timeStart
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
|
|
||||||
{
|
|
||||||
mDecodeEventType = decodeEventType;
|
|
||||||
mTimeStart = timeStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the encrypted state for the channel/timeslot
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantDecodeEventBuilder encrypted(boolean encrypted)
|
|
||||||
{
|
|
||||||
mEncrypted = encrypted;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the duration value
|
|
||||||
* @param duration in milliseconds
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantDecodeEventBuilder duration(long duration)
|
|
||||||
{
|
|
||||||
mDuration = duration;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the duration value using the end - start timestamps
|
|
||||||
* @param timestamp for end of event
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantDecodeEventBuilder end(long timestamp)
|
|
||||||
{
|
|
||||||
mDuration = timestamp - mTimeStart;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the channel descriptor for this event
|
|
||||||
* @param channel
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel)
|
|
||||||
{
|
|
||||||
mChannel = channel;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the identifier collection.
|
|
||||||
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
|
|
||||||
* alias list configuration name.
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantDecodeEventBuilder identifiers(IdentifierCollection identifierCollection)
|
|
||||||
{
|
|
||||||
mIdentifierCollection = identifierCollection;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the details for the event
|
|
||||||
* @param details
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantDecodeEventBuilder details(String details)
|
|
||||||
{
|
|
||||||
mDetails = details;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the decode event
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantEvent build()
|
|
||||||
{
|
|
||||||
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mDecodeEventType, mTimeStart);
|
|
||||||
decodeEvent.setChannelDescriptor(mChannel);
|
|
||||||
decodeEvent.setTimeslot(mChannel.getTimeslot());
|
|
||||||
decodeEvent.setDetails(mDetails);
|
|
||||||
decodeEvent.setDuration(mDuration);
|
|
||||||
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
|
||||||
return decodeEvent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraTraff
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxGroupVoiceChannelUpdate;
|
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.CapacityPlusCSBKO_60;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusCSBKO_60;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowAnnouncement;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowAnnouncement;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowGrant;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowGrant;
|
||||||
@@ -278,8 +279,11 @@ public class CSBKMessageFactory
|
|||||||
case MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT:
|
case MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT:
|
||||||
csbk = new CapacityPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new CapacityPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE:
|
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||||
csbk = new CapacityMaxGroupVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new CapacityMaxOpenModeVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE:
|
||||||
|
csbk = new CapacityMaxAdvantageModeVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_CSBKO_10:
|
case MOTOROLA_CONPLUS_CSBKO_10:
|
||||||
csbk = new ConnectPlusOTAAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new ConnectPlusOTAAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
|||||||
@@ -78,8 +78,10 @@ public enum Opcode
|
|||||||
MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"),
|
MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"),
|
||||||
|
|
||||||
MOTOROLA_CAPMAX_GROUP_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, 16, "GROUP CHANNEL USER"),
|
MOTOROLA_CAPMAX_GROUP_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, 16, "GROUP CHANNEL USER"),
|
||||||
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
|
MOTOROLA_CAPMAX_TALKER_ALIAS(Vendor.MOTOROLA_CAPACITY_PLUS, 20, "CAP MAX TALKER ALIAS"),
|
||||||
MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA_CAPACITY_PLUS, 33, "TALKGROUP ACTIVE"),
|
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "CAP MAX ALOHA"),
|
||||||
|
MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE(Vendor.MOTOROLA_CAPACITY_PLUS, 33, "CAP MAX CHAN UPD OPEN MODE"),
|
||||||
|
MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE(Vendor.MOTOROLA_CAPACITY_PLUS, 34, "CAP MAX CHAN UPD ADV MODE"),
|
||||||
|
|
||||||
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"),
|
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"),
|
||||||
MOTOROLA_CAPPLUS_CALL_ALERT_ACK(Vendor.MOTOROLA_CAPACITY_PLUS, 32, "CALL ALERT ACK"),
|
MOTOROLA_CAPPLUS_CALL_ALERT_ACK(Vendor.MOTOROLA_CAPACITY_PLUS, 32, "CALL ALERT ACK"),
|
||||||
|
|||||||
@@ -0,0 +1,333 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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 <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motorola Capacity Max Advantage Mode - Voice Channel Update (Opcode 34 / 0x22)
|
||||||
|
* Note: it appears that Capacity Max Talkgroups in advantage mode are only 10-bits long in range of 0 - 1023.
|
||||||
|
*/
|
||||||
|
public class CapacityMaxAdvantageModeVoiceChannelUpdate extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||||
|
{
|
||||||
|
private static final int[] CHANNEL_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
|
||||||
|
private static final int[] TALKGROUP_CH1_TS1 = new int[]{28, 29, 30, 31, 32, 33, 34, 35, 36, 37};
|
||||||
|
private static final int[] TALKGROUP_CH1_TS2 = new int[]{38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] CHANNEL_2 = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59};
|
||||||
|
private static final int[] TALKGROUP_CH2_TS1 = new int[]{60, 61, 62, 63, 64, 65, 66, 67, 68, 69};
|
||||||
|
private static final int[] TALKGROUP_CH2_TS2 = new int[]{70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
private TalkgroupIdentifier mTalkgroupCH1TS1;
|
||||||
|
private TalkgroupIdentifier mTalkgroupCH1TS2;
|
||||||
|
private TalkgroupIdentifier mTalkgroupCH2TS1;
|
||||||
|
private TalkgroupIdentifier mTalkgroupCH2TS2;
|
||||||
|
private DMRTier3Channel mChannel1TS1;
|
||||||
|
private DMRTier3Channel mChannel1TS2;
|
||||||
|
private DMRTier3Channel mChannel2TS1;
|
||||||
|
private DMRTier3Channel mChannel2TS2;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public CapacityMaxAdvantageModeVoiceChannelUpdate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(hasRAS())
|
||||||
|
{
|
||||||
|
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" CSBK CAP-MAX MODE:ADVANTAGE CHANNEL UPDATE");
|
||||||
|
|
||||||
|
if(hasChannel1Timeslot1())
|
||||||
|
{
|
||||||
|
sb.append(" TG1:").append(getTalkgroupCH1TS1());
|
||||||
|
sb.append(" ON").append(getChannel1TS1());
|
||||||
|
}
|
||||||
|
if(hasChannel1Timeslot2())
|
||||||
|
{
|
||||||
|
sb.append(" TG2:").append(getTalkgroupCH1TS2());
|
||||||
|
sb.append(" ON").append(getChannel1TS2());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasChannel2Timeslot1())
|
||||||
|
{
|
||||||
|
sb.append(" TG3:").append(getTalkgroupCH1TS1());
|
||||||
|
sb.append(" ON").append(getChannel2TS1());
|
||||||
|
}
|
||||||
|
if(hasChannel2Timeslot2())
|
||||||
|
{
|
||||||
|
sb.append(" TG4:").append(getTalkgroupCH1TS2());
|
||||||
|
sb.append(" ON").append(getChannel2TS2());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message contains activity on channel 1 timeslot 1.
|
||||||
|
*/
|
||||||
|
public boolean hasChannel1Timeslot1()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(TALKGROUP_CH1_TS1) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message contains activity on channel 1 timeslot 2.
|
||||||
|
*/
|
||||||
|
public boolean hasChannel1Timeslot2()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(TALKGROUP_CH1_TS2) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message contains activity on channel 2 timeslot 1.
|
||||||
|
*/
|
||||||
|
public boolean hasChannel2Timeslot1()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(TALKGROUP_CH2_TS1) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message contains activity on channel 2 timeslot 2.
|
||||||
|
*/
|
||||||
|
public boolean hasChannel2Timeslot2()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(TALKGROUP_CH2_TS2) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup that is active on channel 1 timeslot 1.
|
||||||
|
*/
|
||||||
|
public TalkgroupIdentifier getTalkgroupCH1TS1()
|
||||||
|
{
|
||||||
|
if(mTalkgroupCH1TS1 == null)
|
||||||
|
{
|
||||||
|
mTalkgroupCH1TS1 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_CH1_TS1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkgroupCH1TS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup that is active on channel 1 timeslot 2.
|
||||||
|
*/
|
||||||
|
public TalkgroupIdentifier getTalkgroupCH1TS2()
|
||||||
|
{
|
||||||
|
if(mTalkgroupCH1TS2 == null)
|
||||||
|
{
|
||||||
|
mTalkgroupCH1TS2 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_CH1_TS2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkgroupCH1TS2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup that is active on channel 2 timeslot 1.
|
||||||
|
*/
|
||||||
|
public TalkgroupIdentifier getTalkgroupCH2TS1()
|
||||||
|
{
|
||||||
|
if(mTalkgroupCH2TS1 == null)
|
||||||
|
{
|
||||||
|
mTalkgroupCH2TS1 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_CH2_TS1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkgroupCH2TS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup that is active on channel 2 timeslot 2.
|
||||||
|
*/
|
||||||
|
public TalkgroupIdentifier getTalkgroupCH2TS2()
|
||||||
|
{
|
||||||
|
if(mTalkgroupCH2TS2 == null)
|
||||||
|
{
|
||||||
|
mTalkgroupCH2TS2 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_CH2_TS2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkgroupCH2TS2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel 1 Timeslot 1.
|
||||||
|
*/
|
||||||
|
public DMRTier3Channel getChannel1TS1()
|
||||||
|
{
|
||||||
|
if(mChannel1TS1 == null)
|
||||||
|
{
|
||||||
|
mChannel1TS1 = new DMRTier3Channel(getMessage().getInt(CHANNEL_1), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannel1TS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel 1 Timeslot 2.
|
||||||
|
*/
|
||||||
|
public DMRTier3Channel getChannel1TS2()
|
||||||
|
{
|
||||||
|
if(mChannel1TS2 == null)
|
||||||
|
{
|
||||||
|
mChannel1TS2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_1), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannel1TS2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel 2 Timeslot 1.
|
||||||
|
*/
|
||||||
|
public DMRTier3Channel getChannel2TS1()
|
||||||
|
{
|
||||||
|
if(mChannel2TS1 == null)
|
||||||
|
{
|
||||||
|
mChannel2TS1 = new DMRTier3Channel(getMessage().getInt(CHANNEL_2), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannel2TS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel 2 Timeslot 2.
|
||||||
|
*/
|
||||||
|
public DMRTier3Channel getChannel2TS2()
|
||||||
|
{
|
||||||
|
if(mChannel2TS2 == null)
|
||||||
|
{
|
||||||
|
mChannel2TS2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_2), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannel2TS2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns a timeslot frequency map for the DMR channel
|
||||||
|
*
|
||||||
|
* @param timeslotFrequencies that match the logical timeslots
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
|
{
|
||||||
|
if(hasChannel1Timeslot1())
|
||||||
|
{
|
||||||
|
getChannel1TS1().apply(timeslotFrequencies);
|
||||||
|
}
|
||||||
|
if(hasChannel1Timeslot2())
|
||||||
|
{
|
||||||
|
getChannel1TS2().apply(timeslotFrequencies);
|
||||||
|
}
|
||||||
|
if(hasChannel2Timeslot1())
|
||||||
|
{
|
||||||
|
getChannel2TS1().apply(timeslotFrequencies);
|
||||||
|
}
|
||||||
|
if(hasChannel2Timeslot2())
|
||||||
|
{
|
||||||
|
getChannel2TS2().apply(timeslotFrequencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getLogicalChannelNumbers()
|
||||||
|
{
|
||||||
|
//Since both timeslots share the same LCN, we use just TS1's channel for both channel 1 and channel 2
|
||||||
|
if(hasChannel1Timeslot1() && hasChannel2Timeslot1())
|
||||||
|
{
|
||||||
|
return new int[]{getChannel1TS1().getChannelId(), getChannel2TS1().getChannelId()};
|
||||||
|
}
|
||||||
|
else if(hasChannel1Timeslot1())
|
||||||
|
{
|
||||||
|
return new int[]{getChannel1TS1().getChannelId()};
|
||||||
|
}
|
||||||
|
else if(hasChannel2Timeslot1())
|
||||||
|
{
|
||||||
|
return new int[]{getChannel2TS1().getChannelId()};
|
||||||
|
}
|
||||||
|
return getChannel1TS1().getLogicalChannelNumbers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
|
||||||
|
if(hasChannel1Timeslot1())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getTalkgroupCH1TS1());
|
||||||
|
mIdentifiers.add(getChannel1TS1());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasChannel1Timeslot2())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getTalkgroupCH1TS2());
|
||||||
|
mIdentifiers.add(getChannel1TS2());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasChannel2Timeslot1())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getTalkgroupCH2TS1());
|
||||||
|
mIdentifiers.add(getChannel2TS1());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasChannel2Timeslot2())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getTalkgroupCH2TS2());
|
||||||
|
mIdentifiers.add(getChannel2TS2());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,153 +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 <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Motorola Capacity Max - Group Voice Channel Update (Opcode 33 / 0x21)
|
|
||||||
*
|
|
||||||
* Likely incomplete implementation.
|
|
||||||
*/
|
|
||||||
public class CapacityMaxGroupVoiceChannelUpdate extends CSBKMessage implements ITimeslotFrequencyReceiver
|
|
||||||
{
|
|
||||||
private static final int[] CHANNEL_NUMBER = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
|
|
||||||
private static final int[] TIMESLOT = new int[]{28}; //Logical guess - not seeing it set in the example Tier3 recording
|
|
||||||
|
|
||||||
private static final int[] TALKGROUP = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
|
||||||
private TalkgroupIdentifier mTalkgroup;
|
|
||||||
private DMRTier3Channel mChannel;
|
|
||||||
private List<Identifier> mIdentifiers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance
|
|
||||||
*
|
|
||||||
* @param syncPattern for the CSBK
|
|
||||||
* @param message bits
|
|
||||||
* @param cach for the DMR burst
|
|
||||||
* @param slotType for this message
|
|
||||||
* @param timestamp
|
|
||||||
* @param timeslot
|
|
||||||
*/
|
|
||||||
public CapacityMaxGroupVoiceChannelUpdate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
|
||||||
{
|
|
||||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
if(!isValid())
|
|
||||||
{
|
|
||||||
sb.append("[CRC-ERROR] ");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
|
||||||
if(hasRAS())
|
|
||||||
{
|
|
||||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(" CSBK CAP-MAX GROUP VOICE CHANNEL UPDATE TALKGROUP:").append(getTalkgroup());
|
|
||||||
sb.append(" CHANNEL:").append(getChannel());
|
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Talkgroup that is active on the channel.
|
|
||||||
*/
|
|
||||||
public TalkgroupIdentifier getTalkgroup()
|
|
||||||
{
|
|
||||||
if(mTalkgroup == null)
|
|
||||||
{
|
|
||||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
|
|
||||||
}
|
|
||||||
|
|
||||||
return mTalkgroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Channel the talkgroup is using.
|
|
||||||
* @return channel.
|
|
||||||
*/
|
|
||||||
public DMRTier3Channel getChannel()
|
|
||||||
{
|
|
||||||
if(mChannel == null)
|
|
||||||
{
|
|
||||||
mChannel = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), getChannelTimeslot());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timeslot
|
|
||||||
*/
|
|
||||||
private int getChannelTimeslot()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(TIMESLOT) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns a timeslot frequency map for the DMR channel
|
|
||||||
*
|
|
||||||
* @param timeslotFrequencies that match the logical timeslots
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
|
||||||
{
|
|
||||||
getChannel().apply(timeslotFrequencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int[] getLogicalChannelNumbers()
|
|
||||||
{
|
|
||||||
return getChannel().getLogicalChannelNumbers();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Identifier> getIdentifiers()
|
|
||||||
{
|
|
||||||
if(mIdentifiers == null)
|
|
||||||
{
|
|
||||||
mIdentifiers = new ArrayList<>();
|
|
||||||
mIdentifiers.add(getTalkgroup());
|
|
||||||
mIdentifiers.add(getChannel());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mIdentifiers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motorola Capacity Max Open System Mode - Voice Channel Update (Opcode 33 / 0x21)
|
||||||
|
*
|
||||||
|
* Opcode 33 and 34 are very similar. However, Opcode 33 appears to support full 24-bit group identifiers whereas
|
||||||
|
* Opcode 34 only supports 10-bit group identifiers.
|
||||||
|
* <p>
|
||||||
|
* Likely incomplete implementation.
|
||||||
|
*/
|
||||||
|
public class CapacityMaxOpenModeVoiceChannelUpdate extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||||
|
{
|
||||||
|
private static final int[] CHANNEL_NUMBER = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
|
||||||
|
private static final int[] TALKGROUP_TS1 = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] TALKGROUP_TS2 = new int[]{56, 57, 58, 59, 60, 61, 63, 64, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
private TalkgroupIdentifier mTalkgroupTS1;
|
||||||
|
private TalkgroupIdentifier mTalkgroupTS2;
|
||||||
|
private DMRTier3Channel mChannelTS1;
|
||||||
|
private DMRTier3Channel mChannelTS2;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public CapacityMaxOpenModeVoiceChannelUpdate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(hasRAS())
|
||||||
|
{
|
||||||
|
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" CSBK CAP-MAX MODE:OPEN-SYSTEM CHANNEL UPDATE");
|
||||||
|
|
||||||
|
if(hasTimeslot1())
|
||||||
|
{
|
||||||
|
sb.append(" TG1:").append(getTalkgroupTS1());
|
||||||
|
sb.append(" ON").append(getChannelTS1());
|
||||||
|
}
|
||||||
|
if(hasTimeslot2())
|
||||||
|
{
|
||||||
|
sb.append(" TG2:").append(getTalkgroupTS2());
|
||||||
|
sb.append(" ON").append(getChannelTS2());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message contains channel update for timeslot 1.
|
||||||
|
*/
|
||||||
|
public boolean hasTimeslot1()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(TALKGROUP_TS1) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message contains channel update for timeslot 2.
|
||||||
|
*/
|
||||||
|
public boolean hasTimeslot2()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(TALKGROUP_TS2) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup that is active on the channel for timeslot 1.
|
||||||
|
*/
|
||||||
|
public TalkgroupIdentifier getTalkgroupTS1()
|
||||||
|
{
|
||||||
|
if(mTalkgroupTS1 == null)
|
||||||
|
{
|
||||||
|
mTalkgroupTS1 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_TS1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkgroupTS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup that is active on the channel for timeslot 2.
|
||||||
|
*/
|
||||||
|
public TalkgroupIdentifier getTalkgroupTS2()
|
||||||
|
{
|
||||||
|
if(mTalkgroupTS2 == null)
|
||||||
|
{
|
||||||
|
mTalkgroupTS2 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_TS2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkgroupTS2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel for timeslot 1
|
||||||
|
*/
|
||||||
|
public DMRTier3Channel getChannelTS1()
|
||||||
|
{
|
||||||
|
if(mChannelTS1 == null)
|
||||||
|
{
|
||||||
|
mChannelTS1 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannelTS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel for timeslot 2
|
||||||
|
*/
|
||||||
|
public DMRTier3Channel getChannelTS2()
|
||||||
|
{
|
||||||
|
if(mChannelTS2 == null)
|
||||||
|
{
|
||||||
|
mChannelTS2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mChannelTS2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns a timeslot frequency map for the DMR channel
|
||||||
|
*
|
||||||
|
* @param timeslotFrequencies that match the logical timeslots
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
|
{
|
||||||
|
if(hasTimeslot1())
|
||||||
|
{
|
||||||
|
getChannelTS1().apply(timeslotFrequencies);
|
||||||
|
}
|
||||||
|
if(hasTimeslot2())
|
||||||
|
{
|
||||||
|
getChannelTS2().apply(timeslotFrequencies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getLogicalChannelNumbers()
|
||||||
|
{
|
||||||
|
boolean ts1 = hasTimeslot1();
|
||||||
|
boolean ts2 = hasTimeslot2();
|
||||||
|
|
||||||
|
if(ts1 && ts2)
|
||||||
|
{
|
||||||
|
return new int[]{getChannelTS1().getChannelNumber(), getChannelTS2().getChannelNumber()};
|
||||||
|
}
|
||||||
|
else if(ts1)
|
||||||
|
{
|
||||||
|
return new int[]{getChannelTS1().getChannelNumber()};
|
||||||
|
}
|
||||||
|
else if(ts2)
|
||||||
|
{
|
||||||
|
return new int[]{getChannelTS2().getChannelNumber()};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
|
||||||
|
if(hasTimeslot1())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getChannelTS1());
|
||||||
|
mIdentifiers.add(getTalkgroupTS1());
|
||||||
|
}
|
||||||
|
if(hasTimeslot2())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getChannelTS2());
|
||||||
|
mIdentifiers.add(getTalkgroupTS2());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMe
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAlias;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAliasContinuation;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
@@ -141,6 +143,12 @@ public class LCMessageFactory
|
|||||||
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
||||||
flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot);
|
flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
case FULL_CAPACITY_MAX_TALKER_ALIAS:
|
||||||
|
flc = new CapacityMaxTalkerAlias(message, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
|
||||||
|
flc = new CapacityMaxTalkerAliasContinuation(message, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
|
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
|
||||||
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
|
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ public enum LCOpcode
|
|||||||
FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"),
|
FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"),
|
||||||
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
|
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
|
||||||
FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 16, "CAPMAX GROUP VOICE CHANNEL USER"),
|
FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 16, "CAPMAX GROUP VOICE CHANNEL USER"),
|
||||||
|
FULL_CAPACITY_MAX_TALKER_ALIAS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 20, "CAPMAX TALKER ALIAS"),
|
||||||
|
FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION(Vendor.MOTOROLA_CAPACITY_PLUS, true, 21, "CAPMAX TALKER ALIAS CONTINUATION"),
|
||||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||||
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
|
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
|
||||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capacity Max Talker Alias (FLCO 20 / FID 0x10)
|
||||||
|
* <p>
|
||||||
|
* See also: FLCO 21 - Talker Alias Continuation
|
||||||
|
*/
|
||||||
|
public class CapacityMaxTalkerAlias extends CapacityPlusVoiceChannelUser
|
||||||
|
{
|
||||||
|
private static final int[] ENCODING_TYPE = new int[]{16, 17, 18};
|
||||||
|
private static final int[] LENGTH = new int[]{19, 20, 21, 22};
|
||||||
|
private static final int ALIAS_START = 24;
|
||||||
|
private static final int ALIAS_END = ALIAS_START + (6 * 8); //6 characters?
|
||||||
|
private DmrTalkerAliasIdentifier mTalkerAliasIdentifier;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
*
|
||||||
|
* @param message for the link control payload
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public CapacityMaxTalkerAlias(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLength()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
if(isEncrypted())
|
||||||
|
{
|
||||||
|
sb.append(" *ENCRYPTED*");
|
||||||
|
}
|
||||||
|
if(isReservedBitSet())
|
||||||
|
{
|
||||||
|
sb.append(" *RESERVED-BIT*");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("FLC MOTOROLA CAPMAX TALKER ALIAS:").append(getTalkerAliasIdentifier());
|
||||||
|
sb.append(" LENGTH:").append(getLength());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias identifier
|
||||||
|
*
|
||||||
|
* @return identifier
|
||||||
|
*/
|
||||||
|
public DmrTalkerAliasIdentifier getTalkerAliasIdentifier()
|
||||||
|
{
|
||||||
|
if(mTalkerAliasIdentifier == null)
|
||||||
|
{
|
||||||
|
String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim();
|
||||||
|
mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkerAliasIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getTalkerAliasIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.alias.DmrTalkerAlias2Identifier;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capacity Max Talker Alias Continuation (FLCO 21 / FID 0x10)
|
||||||
|
*/
|
||||||
|
public class CapacityMaxTalkerAliasContinuation extends CapacityPlusVoiceChannelUser
|
||||||
|
{
|
||||||
|
private static final int ALIAS_START = 16;
|
||||||
|
private static final int ALIAS_END = ALIAS_START + (7 * 8);
|
||||||
|
private DmrTalkerAlias2Identifier mTalkerAliasIdentifier;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
*
|
||||||
|
* @param message for the link control payload
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public CapacityMaxTalkerAliasContinuation(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
if(isEncrypted())
|
||||||
|
{
|
||||||
|
sb.append(" *ENCRYPTED*");
|
||||||
|
}
|
||||||
|
if(isReservedBitSet())
|
||||||
|
{
|
||||||
|
sb.append(" *RESERVED-BIT*");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("FLC MOTOROLA CAPMAX TALKER ALIAS CONTINUED:").append(getTalkerAliasIdentifier());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias identifier
|
||||||
|
* @return identifier
|
||||||
|
*/
|
||||||
|
public DmrTalkerAlias2Identifier getTalkerAliasIdentifier()
|
||||||
|
{
|
||||||
|
if(mTalkerAliasIdentifier == null)
|
||||||
|
{
|
||||||
|
String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim();
|
||||||
|
mTalkerAliasIdentifier = DmrTalkerAlias2Identifier.create(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkerAliasIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getTalkerAliasIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,7 +88,8 @@ public abstract class ShortLCMessage extends LCMessage implements IMessage
|
|||||||
{
|
{
|
||||||
Vendor vendor = null;
|
Vendor vendor = null;
|
||||||
|
|
||||||
if(binaryMessage.get(VENDOR_FLAG))
|
//If the first bit is set, consider this a vendor variant, unless the opcode is 10 for Connect Plus Control Channel.
|
||||||
|
if(binaryMessage.get(VENDOR_FLAG) && binaryMessage.getInt(OPCODE) != 10)
|
||||||
{
|
{
|
||||||
vendor = Vendor.fromValue(binaryMessage.getInt(VENDOR));
|
vendor = Vendor.fromValue(binaryMessage.getInt(VENDOR));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,15 @@ public enum DecodeEventType
|
|||||||
DATA_CALLS.contains(this) || REGISTRATION.contains(this);
|
DATA_CALLS.contains(this) || REGISTRATION.contains(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this is a call event.
|
||||||
|
* @return true if this is a call event type.
|
||||||
|
*/
|
||||||
|
public boolean isCallEvent()
|
||||||
|
{
|
||||||
|
return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || DATA_CALLS.contains(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label or pretty value for the element
|
* Label or pretty value for the element
|
||||||
* @return label
|
* @return label
|
||||||
|
|||||||
Reference in New Issue
Block a user