mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -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
|
||||
* @param from
|
||||
* @param to
|
||||
* @param from inclusive
|
||||
* @param to exclusive
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@@ -1198,9 +1198,22 @@ public class BinaryMessage extends BitSet
|
||||
{
|
||||
byte[] bytes = new byte[characterCount];
|
||||
|
||||
int index;
|
||||
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);
|
||||
|
||||
@@ -241,7 +241,8 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
||||
}
|
||||
else
|
||||
{
|
||||
String value = id.getValue().replace(" TS0", "").replace(" TS1", "");
|
||||
String value = id.getValue().replace(" TS1", "")
|
||||
.replace(" TS2", "");
|
||||
return value + " TS:" + channelMetadata.getTimeslot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public abstract class DecoderState extends AbstractDecoderState
|
||||
/**
|
||||
* Sets the current channel descriptor
|
||||
*/
|
||||
protected void setCurrentChannel(IChannelDescriptor channel)
|
||||
public void setCurrentChannel(IChannelDescriptor channel)
|
||||
{
|
||||
mCurrentChannel = channel;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ public enum Form
|
||||
STATE,
|
||||
SYSTEM,
|
||||
TALKER_ALIAS,
|
||||
TALKER_ALIAS_2,
|
||||
TALKGROUP,
|
||||
TELEPHONE_NUMBER,
|
||||
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.DecodeConfigDMR;
|
||||
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||
import io.github.dsheirer.module.decode.dmr.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.Fleetsync2DecoderState;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
|
||||
@@ -416,22 +419,50 @@ public class DecoderFactory
|
||||
|
||||
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(trafficChannelManager instanceof DMRTrafficChannelManager)
|
||||
{
|
||||
dmrTrafficChannelManager = (DMRTrafficChannelManager)trafficChannelManager;
|
||||
}
|
||||
else
|
||||
{
|
||||
dmrTrafficChannelManager = new DMRTrafficChannelManager(channel);
|
||||
}
|
||||
|
||||
modules.add(dmrTrafficChannelManager);
|
||||
}
|
||||
|
||||
modules.add(new DMRDecoderState(channel, 1, dmrTrafficChannelManager));
|
||||
modules.add(new DMRDecoderState(channel, 2, dmrTrafficChannelManager));
|
||||
DMRDecoderState state1 = new DMRDecoderState(channel, 1, 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, 2));
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.log.LoggingSuppressor;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||
@@ -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.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.CapacityMaxOpenModeVoiceChannelUpdate;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAdvantageModeVoiceChannelUpdate;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusNeighbors;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusSiteStatus;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusDataChannelGrant;
|
||||
@@ -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.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.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.CapacityPlusEncryptedVoiceChannelUser;
|
||||
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.event.DecodeEvent;
|
||||
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.ip.hytera.rrs.HyteraRrsPacket;
|
||||
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.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Decoder state for an DMR channel. Maintains the call/data/idle state of the channel and produces events by
|
||||
* monitoring the decoded message stream.
|
||||
@@ -106,16 +110,16 @@ import org.slf4j.LoggerFactory;
|
||||
public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
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 AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
||||
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
||||
private Channel mChannel;
|
||||
private DMRNetworkConfigurationMonitor mNetworkConfigurationMonitor;
|
||||
private DMRTrafficChannelManager mTrafficChannelManager;
|
||||
private DecodeEvent mCurrentCallEvent;
|
||||
private long mCurrentFrequency;
|
||||
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.
|
||||
@@ -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.
|
||||
* @param message to check
|
||||
@@ -161,6 +176,16 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
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
|
||||
* 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();
|
||||
|
||||
mCurrentFrequency = 0;
|
||||
mDetectedCallEventsMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,6 +331,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details("MESSAGE: " + sms.getSMS())
|
||||
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
@@ -331,6 +356,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
mic.update(hyteraSmsPacket.getDestination());
|
||||
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details("SMS:" + hyteraSmsPacket.getSMS())
|
||||
@@ -346,6 +372,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
sb.append("HYTERA RRS REGISTER RADIO:");
|
||||
sb.append(rrs.getDestination());
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(sb.toString())
|
||||
@@ -371,6 +398,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(sb.toString())
|
||||
@@ -383,6 +411,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.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());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(ars.toString())
|
||||
@@ -407,6 +437,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(lrrp.toString())
|
||||
@@ -419,6 +450,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(xcmp.toString())
|
||||
@@ -428,6 +460,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
else
|
||||
{
|
||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.details(packet.toString())
|
||||
@@ -760,15 +793,17 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_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_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_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 = dataGrant.getChannel();
|
||||
|
||||
DMRChannel channel = channelGrant.getChannel();
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
@@ -778,90 +813,13 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
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);
|
||||
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
if(csbk instanceof ChannelGrant)
|
||||
else
|
||||
{
|
||||
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
||||
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);
|
||||
}
|
||||
mLog.error("Unrecognized DMR channel grant CSBK ignored: " + csbk.getClass());
|
||||
}
|
||||
break;
|
||||
case MOTOROLA_CAPMAX_ALOHA:
|
||||
@@ -877,42 +835,113 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT:
|
||||
if(csbk instanceof ConnectPlusDataChannelGrant)
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||
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();
|
||||
|
||||
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.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);
|
||||
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_REGISTRATION_REQUEST:
|
||||
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details("Registration Request")
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
@@ -922,6 +951,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
||||
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details("Registration Response")
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
@@ -930,41 +960,27 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
|
||||
if(csbk instanceof ConnectPlusVoiceChannelUser)
|
||||
if(csbk instanceof ConnectPlusVoiceChannelUser cpvcu)
|
||||
{
|
||||
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
||||
DMRChannel channel = cpvcu.getChannel();
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
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);
|
||||
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_TALKGROUP_AFFILIATION:
|
||||
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details("TALKGROUP AFFILIATION")
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.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) {
|
||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.details(details)
|
||||
@@ -1010,82 +1017,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
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
|
||||
* @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)
|
||||
{
|
||||
mCurrentCallEvent.setDetails(ep.getDetails());
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1114,6 +1046,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cpgvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1131,6 +1064,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cpgvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1148,6 +1082,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cmvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1160,6 +1095,28 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
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:
|
||||
if(message instanceof CapacityPlusWideAreaVoiceChannelUser)
|
||||
{
|
||||
@@ -1169,6 +1126,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cpuo4.getTalkgroup());
|
||||
}
|
||||
@@ -1185,6 +1143,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(hgvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1209,6 +1168,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(huuvcu.getTargetRadio());
|
||||
}
|
||||
@@ -1228,6 +1188,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(gvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1247,6 +1208,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(uuvcu.getTargetRadio());
|
||||
}
|
||||
@@ -1336,8 +1298,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.timeslot(getTimeslot())
|
||||
.build();
|
||||
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1348,9 +1308,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
|
||||
mCurrentCallEvent.end(timestamp);
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
|
||||
broadcast(mCurrentCallEvent);
|
||||
|
||||
if(type == DecodeEventType.CALL_GROUP_ENCRYPTED || type == DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, event, State.ENCRYPTED, getTimeslot()));
|
||||
@@ -1403,7 +1364,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
mCurrentFrequency = event.getFrequency();
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
mTrafficChannelManager.setCurrentControlFrequency(mCurrentFrequency);
|
||||
mTrafficChannelManager.setCurrentControlFrequency(mCurrentFrequency, mChannel);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -59,6 +59,8 @@ public class DMRNetworkConfigurationMonitor
|
||||
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_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 Map<Integer,AdjacentSiteInformation> mTier3NeighborSites = new HashMap<>();
|
||||
@@ -67,6 +69,7 @@ public class DMRNetworkConfigurationMonitor
|
||||
private DMRSite mDMRSite;
|
||||
private Model mTier3Model;
|
||||
private String mBrand;
|
||||
private String mMode;
|
||||
private Integer mColorCode;
|
||||
private DMRChannel mCurrentChannel;
|
||||
private Channel mChannel;
|
||||
@@ -94,13 +97,13 @@ public class DMRNetworkConfigurationMonitor
|
||||
*/
|
||||
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;
|
||||
case STANDARD_ANNOUNCEMENT:
|
||||
if(csbk instanceof AdjacentSiteInformation neighbor)
|
||||
{
|
||||
mTier3NeighborSites.put(neighbor.getNeighborSystemIdentityCode().getSite().getValue(), neighbor);
|
||||
}
|
||||
break;
|
||||
case HYTERA_08_ANNOUNCEMENT:
|
||||
case HYTERA_68_ANNOUNCEMENT:
|
||||
if(csbk instanceof HyteraAnnouncement)
|
||||
@@ -240,6 +249,12 @@ public class DMRNetworkConfigurationMonitor
|
||||
}
|
||||
}
|
||||
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:
|
||||
if(mNeighborSites.isEmpty() && csbk instanceof ConnectPlusNeighborReport)
|
||||
{
|
||||
@@ -297,6 +312,11 @@ public class DMRNetworkConfigurationMonitor
|
||||
//DMR System Brand Name
|
||||
sb.append("\n\nBrand:").append((mBrand == null ? BRAND_STANDARD : mBrand));
|
||||
|
||||
if(mMode != null)
|
||||
{
|
||||
sb.append("\nConfigured Mode:").append(mMode);
|
||||
}
|
||||
|
||||
if(mDMRNetwork != null)
|
||||
{
|
||||
sb.append("\nNetwork:").append(mDMRNetwork);
|
||||
@@ -347,6 +367,11 @@ public class DMRNetworkConfigurationMonitor
|
||||
{
|
||||
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())
|
||||
{
|
||||
@@ -354,7 +379,16 @@ public class DMRNetworkConfigurationMonitor
|
||||
sb.append("\tNeighbor: ").append("Network:").append(site.getNeighborSystemIdentityCode().getNetwork());
|
||||
sb.append(" Site:").append(site.getNeighborSystemIdentityCode().getSite());
|
||||
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.module.decode.config.DecodeConfiguration;
|
||||
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.message.data.csbk.Opcode;
|
||||
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.tuner.channel.rotation.DisableChannelRotationMonitorRequest;
|
||||
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -63,6 +64,8 @@ import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -83,36 +86,32 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* This manager monitors channel events watching for events related to managed traffic channels.
|
||||
*/
|
||||
public class DMRTrafficChannelManager extends TrafficChannelManager implements IDecodeEventProvider, IChannelEventListener,
|
||||
IChannelEventProvider
|
||||
public class DMRTrafficChannelManager extends TrafficChannelManager implements IChannelEventListener,
|
||||
IChannelEventProvider, IDecodeEventProvider
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRTrafficChannelManager.class);
|
||||
public static final String CHANNEL_START_REJECTED = " REJECTED - NO TUNER";
|
||||
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 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
|
||||
|
||||
private Queue<Channel> mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>();
|
||||
private List<Channel> mManagedTrafficChannels;
|
||||
|
||||
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
|
||||
private Map<DMRChannel,DMRChannelGrantEvent> mChannelGrantEventMap = new ConcurrentHashMap<>();
|
||||
|
||||
private Queue<Channel> mAvailableTrafficChannels = new ConcurrentLinkedQueue<>();
|
||||
private List<Channel> mAllocatedTrafficChannels;
|
||||
private Map<Long,Channel> mAllocatedChannelFrequencyMap = new ConcurrentHashMap<>();
|
||||
private ReentrantLock mLock = new ReentrantLock();
|
||||
private Map<Long,IDecodeEvent> mCallEventsTS1 = new ConcurrentHashMap<>();
|
||||
private Map<Long,IDecodeEvent> mCallEventsTS2 = new ConcurrentHashMap<>();
|
||||
private Listener<ChannelEvent> mChannelEventListener;
|
||||
private Listener<IDecodeEvent> mDecodeEventListener;
|
||||
|
||||
private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor();
|
||||
private Channel mParentChannel;
|
||||
private boolean mIgnoreDataCalls;
|
||||
|
||||
//Used as temporary storage for message and decode event history during Cap+ REST channel rotation
|
||||
private DecodeEventHistory mTransientDecodeEventHistory;
|
||||
private List<IMessage> mTransientMessageHistory;
|
||||
|
||||
private boolean mIgnoreDataCalls;
|
||||
private long mCurrentControlFrequency;
|
||||
|
||||
/**
|
||||
* Monitors call events and allocates traffic decoder channels in response
|
||||
* 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
|
||||
* produce an additional traffic channel allocation.
|
||||
* @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()
|
||||
{
|
||||
if(mManagedTrafficChannels == null)
|
||||
if(mAllocatedTrafficChannels == null)
|
||||
{
|
||||
DecodeConfiguration decodeConfiguration = mParentChannel.getDecodeConfiguration();
|
||||
List<Channel> trafficChannelList = new ArrayList<>();
|
||||
@@ -176,8 +176,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
}
|
||||
}
|
||||
|
||||
mAvailableTrafficChannelQueue.addAll(trafficChannelList);
|
||||
mManagedTrafficChannels = Collections.unmodifiableList(trafficChannelList);
|
||||
mAvailableTrafficChannels.addAll(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,
|
||||
DMRNetworkConfigurationMonitor networkConfigurationMonitor)
|
||||
{
|
||||
//Only do the conversion of the original channel has multiple frequencies defined and the rest channel is
|
||||
//one of those frequencies
|
||||
if(restChannel.getDownlinkFrequency() > 0 &&
|
||||
!mAllocatedTrafficChannelFrequencyMap.containsKey(restChannel.getDownlinkFrequency()) &&
|
||||
channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
SourceConfigTunerMultipleFrequency originalSourceConfig = (SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration();
|
||||
long rest = restChannel.getDownlinkFrequency();
|
||||
|
||||
//Add the rest channel to the list of frequencies in the source configuration
|
||||
if(!originalSourceConfig.getFrequencies().contains(restChannel.getDownlinkFrequency()))
|
||||
//Only do the conversion of the original channel has multiple frequencies defined and the rest channel is
|
||||
//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
|
||||
//to the channel model. The configuration will simply be adjusted at runtime.
|
||||
}
|
||||
|
||||
//Convert current channel to a traffic channel if one is available
|
||||
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
|
||||
|
||||
if(trafficChannel != null && restChannel.getDownlinkFrequency() > 0)
|
||||
{
|
||||
//Disable the channel rotation manager when we have multiple frequencies defined
|
||||
getInterModuleEventBus().post(new DisableChannelRotationMonitorRequest());
|
||||
|
||||
//Set the frequency for the traffic channel configuration
|
||||
SourceConfigTuner trafficSourceConfig = new SourceConfigTuner();
|
||||
trafficSourceConfig.setFrequency(currentFrequency);
|
||||
trafficChannel.setSourceConfiguration(trafficSourceConfig);
|
||||
|
||||
//Post a request for message and decode event history to transfer to the new REST channel. This has
|
||||
// to be posted before the channel conversion request
|
||||
getInterModuleEventBus().post(new DecodeEventHistoryRequest());
|
||||
getInterModuleEventBus().post(new MessageHistoryRequest());
|
||||
|
||||
//Dispatch a request to convert this processing chain to the traffic channel. This will cause the
|
||||
//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
|
||||
//no longer allocate traffic channels.
|
||||
getInterModuleEventBus().post(new ChannelConversionRequest(channel, trafficChannel));
|
||||
|
||||
mAllocatedTrafficChannelFrequencyMap.put(currentFrequency, trafficChannel);
|
||||
|
||||
//Set the preferred frequency to use when restarting the original channel
|
||||
originalSourceConfig.setPreferredFrequency(restChannel.getDownlinkFrequency());
|
||||
|
||||
//Dispatch request to persistently start the original channel with the rest channel frequency and reuse
|
||||
//this traffic channel manager in the new processing chain.
|
||||
ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, this);
|
||||
request.setPersistentAttempt(true);
|
||||
request.setChildDecodeEventHistory(mTransientDecodeEventHistory);
|
||||
|
||||
//If we received an event history response, add it to the request as preload data content
|
||||
if(mTransientDecodeEventHistory != null)
|
||||
{
|
||||
DecodeEventHistoryPreloadData eventHistory =
|
||||
new DecodeEventHistoryPreloadData(mTransientDecodeEventHistory.getItems());
|
||||
request.addPreloadDataContent(eventHistory);
|
||||
|
||||
mTransientDecodeEventHistory = null;
|
||||
}
|
||||
|
||||
//If we received a message history response, add it to the request as preload data content
|
||||
if(mTransientMessageHistory != null)
|
||||
{
|
||||
MessageHistoryPreloadData messageHistory = new MessageHistoryPreloadData(mTransientMessageHistory);
|
||||
request.addPreloadDataContent(messageHistory);
|
||||
mTransientMessageHistory = null;
|
||||
}
|
||||
|
||||
//Add the DMR network configuration monitor as preload data
|
||||
if(networkConfigurationMonitor != null)
|
||||
{
|
||||
request.addPreloadDataContent(new DMRNetworkConfigurationPreloadData(networkConfigurationMonitor));
|
||||
}
|
||||
|
||||
getInterModuleEventBus().post(request);
|
||||
//Add the rest channel to the list of frequencies in the source configuration
|
||||
if(!originalSourceConfig.getFrequencies().contains(restChannel.getDownlinkFrequency()))
|
||||
{
|
||||
originalSourceConfig.addFrequency(restChannel.getDownlinkFrequency());
|
||||
}
|
||||
|
||||
//Convert current channel to a traffic channel if one is available
|
||||
Channel trafficChannel = mAvailableTrafficChannels.poll();
|
||||
|
||||
if(trafficChannel != null)
|
||||
{
|
||||
//Disable the channel rotation manager when we have multiple frequencies defined
|
||||
getInterModuleEventBus().post(new DisableChannelRotationMonitorRequest());
|
||||
|
||||
//Set the frequency for the traffic channel configuration
|
||||
SourceConfigTuner trafficSourceConfig = new SourceConfigTuner();
|
||||
trafficSourceConfig.setFrequency(currentFrequency);
|
||||
trafficChannel.setSourceConfiguration(trafficSourceConfig);
|
||||
|
||||
//Post a request for message and decode event history to transfer to the new REST channel. This has
|
||||
// to be posted before the channel conversion request
|
||||
getInterModuleEventBus().post(new DecodeEventHistoryRequest());
|
||||
getInterModuleEventBus().post(new MessageHistoryRequest());
|
||||
|
||||
//Dispatch a request to convert this processing chain to the traffic channel. This will cause the
|
||||
//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
|
||||
//no longer allocate traffic channels.
|
||||
getInterModuleEventBus().post(new ChannelConversionRequest(channel, trafficChannel));
|
||||
|
||||
mAllocatedChannelFrequencyMap.put(currentFrequency, trafficChannel);
|
||||
|
||||
//Set the preferred frequency to use when restarting the original channel
|
||||
originalSourceConfig.setPreferredFrequency(rest);
|
||||
|
||||
//Dispatch request to persistently start the original channel with the rest channel frequency and reuse
|
||||
//this traffic channel manager in the new processing chain.
|
||||
ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, this);
|
||||
request.setPersistentAttempt(true);
|
||||
request.setChildDecodeEventHistory(mTransientDecodeEventHistory);
|
||||
|
||||
//If we received an event history response, add it to the request as preload data content
|
||||
if(mTransientDecodeEventHistory != null)
|
||||
{
|
||||
DecodeEventHistoryPreloadData eventHistory =
|
||||
new DecodeEventHistoryPreloadData(mTransientDecodeEventHistory.getItems());
|
||||
request.addPreloadDataContent(eventHistory);
|
||||
|
||||
mTransientDecodeEventHistory = null;
|
||||
}
|
||||
|
||||
//If we received a message history response, add it to the request as preload data content
|
||||
if(mTransientMessageHistory != null)
|
||||
{
|
||||
MessageHistoryPreloadData messageHistory = new MessageHistoryPreloadData(mTransientMessageHistory);
|
||||
request.addPreloadDataContent(messageHistory);
|
||||
mTransientMessageHistory = null;
|
||||
}
|
||||
|
||||
//Add the DMR network configuration monitor as preload data
|
||||
if(networkConfigurationMonitor != null)
|
||||
{
|
||||
request.addPreloadDataContent(new DMRNetworkConfigurationPreloadData(networkConfigurationMonitor));
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public void broadcast(DecodeEvent decodeEvent)
|
||||
public void broadcast(IDecodeEvent decodeEvent)
|
||||
{
|
||||
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
|
||||
* decode events for each new channel that is allocated.
|
||||
*
|
||||
*/
|
||||
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
|
||||
Opcode opcode, long timestamp, boolean encrypted)
|
||||
{
|
||||
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||
mLock.lock();
|
||||
|
||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||
try
|
||||
{
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
||||
.channel(channel)
|
||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
boolean allocated = mAllocatedChannelFrequencyMap.containsKey(channel.getDownlinkFrequency());
|
||||
|
||||
mChannelGrantEventMap.put(channel, event);
|
||||
|
||||
}
|
||||
else //Update current event
|
||||
{
|
||||
Identifier from = getIdentifier(identifierCollection, Role.FROM);
|
||||
|
||||
if(from != null)
|
||||
if(allocated)
|
||||
{
|
||||
Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM);
|
||||
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));
|
||||
//Note: if traffic channel is allocated then do nothing & let the channel maintain its own event state
|
||||
}
|
||||
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_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;
|
||||
break;
|
||||
|
||||
@@ -540,7 +589,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
* @param currentIdentifiers to compare against the event
|
||||
* @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)
|
||||
{
|
||||
@@ -609,23 +658,41 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
@Override
|
||||
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
|
||||
public void stop()
|
||||
{
|
||||
mAvailableTrafficChannelQueue.clear();
|
||||
mLock.lock();
|
||||
|
||||
List<Channel> channels = new ArrayList<>(mAllocatedTrafficChannelFrequencyMap.values());
|
||||
|
||||
//Issue a disable request for each traffic channel
|
||||
for(Channel channel: channels)
|
||||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
List<DMRChannel> channelsToRemove = new ArrayList<>();
|
||||
mLock.lock();
|
||||
|
||||
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||
try
|
||||
{
|
||||
if(entry.getKey().getDownlinkFrequency() == frequency)
|
||||
{
|
||||
channelsToRemove.add(entry.getKey());
|
||||
}
|
||||
mCallEventsTS1.remove(frequency);
|
||||
mCallEventsTS2.remove(frequency);
|
||||
}
|
||||
|
||||
for(DMRChannel channel: channelsToRemove)
|
||||
finally
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
for(DMRChannel channel: channelsToUpdate)
|
||||
finally
|
||||
{
|
||||
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||
|
||||
if(event != null)
|
||||
{
|
||||
if(event.getDetails() == null)
|
||||
{
|
||||
event.setDetails(detailFragment);
|
||||
}
|
||||
else if(!event.getDetails().endsWith(detailFragment))
|
||||
{
|
||||
event.setDetails(event.getDetails() + detailFragment);
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
}
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process traffic channel processing stop or processing start rejected events.
|
||||
* @param channelEvent to process
|
||||
*/
|
||||
@Override
|
||||
public synchronized void receive(ChannelEvent channelEvent)
|
||||
{
|
||||
@@ -701,55 +785,72 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
case NOTIFICATION_PROCESSING_STOP:
|
||||
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();
|
||||
removeCallEvents(entry.getKey());
|
||||
break;
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if(frequencyToRemove > 0)
|
||||
finally
|
||||
{
|
||||
mAllocatedTrafficChannelFrequencyMap.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(!mAvailableTrafficChannelQueue.contains(channel))
|
||||
{
|
||||
mAvailableTrafficChannelQueue.add(channel);
|
||||
mLock.unlock();
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_PROCESSING_START_REJECTED:
|
||||
long frequencyToUpdate = 0;
|
||||
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
|
||||
mLock.lock();
|
||||
try
|
||||
{
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
long frequencyToUpdate = 0;
|
||||
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedChannelFrequencyMap.entrySet())
|
||||
{
|
||||
frequencyToUpdate = entry.getKey();
|
||||
updateCallEventDetails(entry.getKey(), CHANNEL_START_REJECTED);
|
||||
break;
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if(frequencyToUpdate > 0)
|
||||
finally
|
||||
{
|
||||
mAllocatedTrafficChannelFrequencyMap.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(!mAvailableTrafficChannelQueue.contains(channel))
|
||||
{
|
||||
mAvailableTrafficChannelQueue.add(channel);
|
||||
mLock.unlock();
|
||||
}
|
||||
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.config.DecodeConfiguration;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -41,6 +42,9 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
||||
private boolean mUseCompressedTalkgroups = false;
|
||||
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
|
||||
|
||||
@JsonIgnore
|
||||
private DecodeEvent mChannelGrantEvent;
|
||||
|
||||
public DecodeConfigDMR()
|
||||
{
|
||||
}
|
||||
@@ -191,4 +195,32 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a channel of the same type, but for the alternate timeslot.
|
||||
* @return sister timeslot channel
|
||||
*/
|
||||
public abstract DMRChannel getSisterTimeslot();
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
@@ -160,6 +166,15 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
mTimeslotFrequency = timeslotFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* TimeslotFrequency for this channel.
|
||||
* @return timeslot frequency instance, or null.
|
||||
*/
|
||||
public TimeslotFrequency getTimeslotFrequency()
|
||||
{
|
||||
return mTimeslotFrequency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
|
||||
@@ -69,4 +69,25 @@ public class DMRLsn extends DMRChannel
|
||||
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());
|
||||
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.HyteraXPTSiteState;
|
||||
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.CapacityPlusDataRevertWindowAnnouncement;
|
||||
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:
|
||||
csbk = new CapacityPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE:
|
||||
csbk = new CapacityMaxGroupVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||
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;
|
||||
case MOTOROLA_CONPLUS_CSBKO_10:
|
||||
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_CAPMAX_GROUP_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, 16, "GROUP CHANNEL USER"),
|
||||
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
|
||||
MOTOROLA_CAPMAX_GROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA_CAPACITY_PLUS, 33, "TALKGROUP ACTIVE"),
|
||||
MOTOROLA_CAPMAX_TALKER_ALIAS(Vendor.MOTOROLA_CAPACITY_PLUS, 20, "CAP MAX TALKER ALIAS"),
|
||||
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_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.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.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.CapacityPlusEncryptedVoiceChannelUser;
|
||||
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:
|
||||
flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot);
|
||||
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:
|
||||
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
|
||||
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_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_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
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -148,6 +148,15 @@ public enum DecodeEventType
|
||||
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
|
||||
* @return label
|
||||
|
||||
Reference in New Issue
Block a user