mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
@@ -74,7 +74,6 @@ import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -95,15 +94,15 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
private DMRTrafficChannelManager mTrafficChannelManager;
|
||||
private DecodeEvent mCurrentCallEvent;
|
||||
private long mCurrentFrequency;
|
||||
private Map<Long,DecodeEvent> mDetectedCallEventsMap = new TreeMap<>();
|
||||
private Map<Integer,DecodeEvent> mDetectedCallEventsMap = new TreeMap<>();
|
||||
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
||||
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
||||
|
||||
/**
|
||||
* Constructs an DMR decoder state with an optional traffic channel manager.
|
||||
* @param channel with configuration details
|
||||
* @param timeslot for this decoder state (1 or 2)
|
||||
* @param trafficChannelManager for handling traffic channel grants.
|
||||
* @param configurationMonitor for tracking activity summary
|
||||
*/
|
||||
public DMRDecoderState(Channel channel, int timeslot, DMRTrafficChannelManager trafficChannelManager)
|
||||
{
|
||||
@@ -111,7 +110,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
mChannel = channel;
|
||||
mTrafficChannelManager = trafficChannelManager;
|
||||
|
||||
//The decoder state passes all messages to the network configuration monitor, so we only contruct
|
||||
//The decoder state passes all messages to the network configuration monitor, so we only construct
|
||||
//the monitor for timeslot 1.
|
||||
if(timeslot == 1)
|
||||
{
|
||||
@@ -181,7 +180,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
if(message.isValid() && message.getTimeslot() == getTimeslot())
|
||||
if(message.getTimeslot() == getTimeslot())
|
||||
{
|
||||
if(message instanceof VoiceMessage)
|
||||
{
|
||||
@@ -191,11 +190,11 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
processData((DataMessage)message);
|
||||
}
|
||||
else if(message instanceof LCMessage)
|
||||
else if(message.isValid() && message instanceof LCMessage)
|
||||
{
|
||||
processLinkControl((LCMessage)message, false);
|
||||
}
|
||||
else if(message instanceof DMRPacketMessage)
|
||||
else if(message.isValid() && message instanceof DMRPacketMessage)
|
||||
{
|
||||
processPacket((DMRPacketMessage)message);
|
||||
}
|
||||
@@ -317,13 +316,16 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
/**
|
||||
* Process Data Messages
|
||||
*
|
||||
* Note: invalid messages are allowed to pass to this method. Messages are selectively checked for isValid()
|
||||
* to overcome RAS implementation in certain systems.
|
||||
*/
|
||||
private void processData(DataMessage message)
|
||||
{
|
||||
switch(message.getSlotType().getDataType())
|
||||
{
|
||||
case CSBK:
|
||||
if(message instanceof CSBKMessage)
|
||||
if(message.isValid() && message instanceof CSBKMessage)
|
||||
{
|
||||
processCSBK((CSBKMessage)message);
|
||||
}
|
||||
@@ -602,12 +604,37 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
ChannelGrant dataGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = dataGrant.getChannel();
|
||||
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()),
|
||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.DATA_CALL.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
event.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
}
|
||||
processCallDetection(dataGrant.getChannel(), dataGrant.getIdentifiers(), dataGrant.getTimestamp(), DecodeEventType.DATA_CALL);
|
||||
}
|
||||
break;
|
||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
@@ -616,12 +643,37 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = tgGrant.getChannel();
|
||||
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()),
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
processCallDetection(tgGrant.getChannel(), tgGrant.getIdentifiers(), tgGrant.getTimestamp(), DecodeEventType.CALL_GROUP);
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.CALL_GROUP.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), 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:
|
||||
@@ -630,13 +682,36 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
ChannelGrant channelGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = channelGrant.getChannel();
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()),
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
processCallDetection(channelGrant.getChannel(), channelGrant.getIdentifiers(),
|
||||
channelGrant.getTimestamp(), DecodeEventType.CALL_UNIT_TO_UNIT);
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.CALL_UNIT_TO_UNIT.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
event.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MOTOROLA_CAPMAX_ALOHA:
|
||||
@@ -664,13 +739,38 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
if(csbk instanceof ConnectPlusDataChannelGrant)
|
||||
{
|
||||
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
|
||||
DMRChannel channel = cpdcg.getChannel();
|
||||
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
mTrafficChannelManager.processChannelGrant(cpdcg.getChannel(),
|
||||
getMergedIdentifierCollection(csbk.getIdentifiers()), csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.DATA_CALL.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
event.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
}
|
||||
processCallDetection(cpdcg.getChannel(), cpdcg.getIdentifiers(), cpdcg.getTimestamp(),
|
||||
DecodeEventType.DATA_CALL);
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
@@ -697,14 +797,39 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
|
||||
if(csbk instanceof ConnectPlusVoiceChannelUser)
|
||||
{
|
||||
DMRChannel channel = ((ConnectPlusVoiceChannelUser)csbk).getChannel();
|
||||
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
||||
DMRChannel channel = cpvcu.getChannel();
|
||||
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
mTrafficChannelManager.processChannelGrant(channel, getMergedIdentifierCollection(csbk.getIdentifiers()),
|
||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
||||
|
||||
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
detectedEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.CALL_GROUP.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
detectedEvent.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(detectedEvent);
|
||||
}
|
||||
processCallDetection(channel, csbk.getIdentifiers(), csbk.getTimestamp(),
|
||||
DecodeEventType.CALL_GROUP);
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
@@ -718,9 +843,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(affiliateEvent);
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case STANDARD_MOVE_TSCC:
|
||||
|
||||
|
||||
default:
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
||||
break;
|
||||
@@ -742,68 +864,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Call Detections.
|
||||
* 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.
|
||||
*
|
||||
* Note: once full support for Connect+ trunk tracking is implemented with the DMRTrafficChannelManager, this will
|
||||
* have to be modified to detect if the trunked channel was allocated or if the call was simply detected. This
|
||||
* would be contingent on having a map of Logical Slot Numbers to frequency for the traffic channel manager to make
|
||||
* an allocation.
|
||||
*
|
||||
* @param channel for the call event
|
||||
* @param identifiers for the call event
|
||||
* @param timestamp of the event or update
|
||||
* @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 void processCallDetection(DMRChannel channel, List<Identifier> identifiers, long timestamp,
|
||||
DecodeEventType eventType)
|
||||
private boolean isStale(DecodeEvent event, long timestamp, List<Identifier> currentIdentifiers)
|
||||
{
|
||||
//Check to see if there is a current call event to see if the detected call event is actually for this timeslot
|
||||
//and we can then identify the LSN for this timeslot
|
||||
if(mCurrentFrequency == 0 && mCurrentCallEvent != null &&
|
||||
isSameCall(mCurrentCallEvent.getIdentifierCollection(), identifiers))
|
||||
if(event == null || (timestamp - event.getTimeStart() > MAX_VALID_CALL_DURATION_MS))
|
||||
{
|
||||
mCurrentFrequency = channel.getDownlinkFrequency();
|
||||
|
||||
if(mNetworkConfigurationMonitor != null)
|
||||
{
|
||||
mNetworkConfigurationMonitor.setCurrentChannel(channel);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
||||
|
||||
if(event == null)
|
||||
{
|
||||
event = DMRDecodeEvent.builder(timestamp)
|
||||
.timeslot(getTimeslot())
|
||||
.identifiers(getMergedIdentifierCollection(identifiers))
|
||||
.eventDescription(eventType.toString())
|
||||
.build();
|
||||
// mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(event.getIdentifierCollection() != null &&
|
||||
isSameCall(event.getIdentifierCollection(), identifiers) &&
|
||||
FastMath.abs(timestamp - event.getTimeStart()) < MAX_VALID_CALL_DURATION_MS)
|
||||
{
|
||||
event.update(timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
event = DMRDecodeEvent.builder(timestamp)
|
||||
.timeslot(getTimeslot())
|
||||
.identifiers(getMergedIdentifierCollection(identifiers))
|
||||
.eventDescription(DecodeEventType.CALL_DETECT.toString())
|
||||
.build();
|
||||
// mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
}
|
||||
|
||||
//Only broadcast the call detect event if it doesn't match the current logical slot number
|
||||
// if(mCurrentLSN == null || mCurrentLSN != channel.getLogicalSlotNumber())
|
||||
// {
|
||||
// broadcast(event);
|
||||
// }
|
||||
return !isSameCall(event.getIdentifierCollection(), currentIdentifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,6 +41,7 @@ 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.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventHistory;
|
||||
@@ -90,8 +91,12 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
IChannelEventProvider
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRTrafficChannelManager.class);
|
||||
public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED";
|
||||
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 long EVENT_TIME_STALE_THRESHOLD = 5000; //5 seconds
|
||||
|
||||
private Queue<Channel> mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>();
|
||||
private List<Channel> mManagedTrafficChannels;
|
||||
@@ -325,7 +330,19 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
|
||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
||||
|
||||
if(event != null && isSameCall(identifierCollection, event.getIdentifierCollection()))
|
||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||
{
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
||||
.channel(channel)
|
||||
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString())
|
||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
|
||||
mLSNGrantEventMap.put(lsn, event);
|
||||
|
||||
}
|
||||
else //Update current event
|
||||
{
|
||||
Identifier from = getIdentifier(identifierCollection, Role.FROM);
|
||||
|
||||
@@ -336,115 +353,98 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
{
|
||||
event.end(timestamp);
|
||||
|
||||
DMRChannelGrantEvent continuationGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
||||
.channel(channel)
|
||||
.eventDescription(getEventType(opcode, event.isEncrypted()).toString() + " - Continue")
|
||||
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString() + " - Continue")
|
||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
|
||||
mLSNGrantEventMap.put(lsn, continuationGrantEvent);
|
||||
broadcast(continuationGrantEvent);
|
||||
mLSNGrantEventMap.put(lsn, 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 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 && frequency != mCurrentControlFrequency &&
|
||||
!mAllocatedTrafficChannelFrequencyMap.containsKey(channel.getDownlinkFrequency()) &&
|
||||
!(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode()))
|
||||
{
|
||||
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
|
||||
|
||||
if(trafficChannel != null)
|
||||
{
|
||||
event.setEventDescription(getEventType(opcode, event.isEncrypted()).toString());
|
||||
event.setDetails("CHANNEL GRANT " + (encrypted ? " ENCRYPTED" : ""));
|
||||
event.setChannelDescriptor(channel);
|
||||
broadcast(event);
|
||||
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(frequency);
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel);
|
||||
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel, identifierCollection));
|
||||
}
|
||||
else
|
||||
{
|
||||
String description = (event.getEventDescription() != null ? event.getEventDescription() : "") +
|
||||
" - MAX TRAFFIC CHANNELS EXCEEDED";
|
||||
event.setEventDescription(description);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
|
||||
{
|
||||
DMRChannelGrantEvent channelGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
||||
.encrypted(encrypted)
|
||||
.channel(channel)
|
||||
.eventDescription(getEventType(opcode, encrypted).toString() + " - Ignored")
|
||||
.details("DATA CALL IGNORED")
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
broadcast(event);
|
||||
|
||||
mLSNGrantEventMap.put(channel.getLogicalSlotNumber(), channelGrantEvent);
|
||||
broadcast(channelGrantEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
DMRChannelGrantEvent channelGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
||||
.encrypted(encrypted)
|
||||
.channel(channel)
|
||||
.eventDescription(getEventType(opcode, encrypted).toString())
|
||||
.details("CHANNEL GRANT")
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
|
||||
mLSNGrantEventMap.put(channel.getLogicalSlotNumber(), channelGrantEvent);
|
||||
|
||||
//Allocate a traffic channel for the downlink frequency if one isn't already allocated
|
||||
//NOTE: we could also allocate a traffic channel for the uplink frequency here, in the future
|
||||
//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 && frequency != mCurrentControlFrequency &&
|
||||
!mAllocatedTrafficChannelFrequencyMap.containsKey(frequency))
|
||||
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.getEventDescription() == null)
|
||||
{
|
||||
event.setEventDescription(getEventType(opcode, identifierCollection, encrypted) + IGNORED);
|
||||
}
|
||||
else if(!event.getEventDescription().endsWith(IGNORED))
|
||||
{
|
||||
event.setEventDescription(event.getEventDescription() + IGNORED);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
String details = (channelGrantEvent.getDetails() != null ? channelGrantEvent.getDetails() : "") +
|
||||
" " + MAX_TRAFFIC_CHANNELS_EXCEEDED;
|
||||
channelGrantEvent.setDetails(details);
|
||||
channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored");
|
||||
}
|
||||
else
|
||||
if(trafficChannel != null)
|
||||
{
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(frequency);
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel);
|
||||
broadcast(ChannelEvent.requestEnable(trafficChannel));
|
||||
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel,
|
||||
identifierCollection));
|
||||
}
|
||||
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(channelGrantEvent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a call event type description for the specified opcode and service options
|
||||
*/
|
||||
private DecodeEventType getEventType(Opcode opcode, boolean encrypted)
|
||||
private DecodeEventType getEventType(Opcode opcode, IdentifierCollection identifierCollection, boolean encrypted)
|
||||
{
|
||||
DecodeEventType type = null;
|
||||
|
||||
@@ -452,10 +452,22 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
{
|
||||
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
|
||||
type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
|
||||
break;
|
||||
|
||||
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
|
||||
Identifier to = identifierCollection.getToIdentifier();
|
||||
|
||||
if(to instanceof DMRRadio)
|
||||
{
|
||||
type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
|
||||
}
|
||||
break;
|
||||
|
||||
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||
type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT;
|
||||
@@ -535,6 +547,31 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
return Objects.equals(toIdentifier1, toIdentifier2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, IdentifierCollection currentIdentifiers)
|
||||
{
|
||||
if(event == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(timestamp - event.getTimeEnd() > EVENT_TIME_STALE_THRESHOLD)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !isSameCall(event.getIdentifierCollection(), currentIdentifiers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the first identifier with a TO role.
|
||||
*
|
||||
@@ -617,9 +654,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
/**
|
||||
* Removes any call events that are associated with the specified frequency.
|
||||
* @param frequency that was removed
|
||||
* @param description to optionally append to the details for the event
|
||||
*/
|
||||
private void cleanupCallEvents(long frequency, String description)
|
||||
private void removeCallEvents(long frequency)
|
||||
{
|
||||
List<Integer> lsnsToRemove = new ArrayList<>();
|
||||
|
||||
@@ -633,20 +669,35 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
|
||||
for(Integer lsn: lsnsToRemove)
|
||||
{
|
||||
DMRChannelGrantEvent event = mLSNGrantEventMap.remove(lsn);
|
||||
mLSNGrantEventMap.remove(lsn);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCallEventDetails(long frequency, String detailFragment)
|
||||
{
|
||||
List<Integer> lsnsToUpdate = new ArrayList<>();
|
||||
|
||||
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
|
||||
{
|
||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
||||
{
|
||||
lsnsToUpdate.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for(Integer lsn: lsnsToUpdate)
|
||||
{
|
||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
||||
|
||||
if(event != null)
|
||||
{
|
||||
if(description != null)
|
||||
if(event.getDetails() == null)
|
||||
{
|
||||
if(event.getEventDescription() != null)
|
||||
{
|
||||
event.setEventDescription(event.getEventDescription() + " - " + description);
|
||||
}
|
||||
else
|
||||
{
|
||||
event.setEventDescription(description);
|
||||
}
|
||||
event.setDetails(detailFragment);
|
||||
}
|
||||
else if(!event.getDetails().endsWith(detailFragment))
|
||||
{
|
||||
event.setDetails(event.getDetails() + detailFragment);
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
@@ -659,36 +710,64 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
{
|
||||
Channel channel = channelEvent.getChannel();
|
||||
|
||||
if(channel.isTrafficChannel() && (channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_STOP ||
|
||||
channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_START_REJECTED))
|
||||
if(channel.isTrafficChannel())
|
||||
{
|
||||
String optionalDescription = (channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_START_REJECTED ?
|
||||
"Rejected" : null);
|
||||
|
||||
long frequencyToRemove = 0;
|
||||
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
|
||||
switch(channelEvent.getEvent())
|
||||
{
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
{
|
||||
frequencyToRemove = entry.getKey();
|
||||
cleanupCallEvents(entry.getKey(), optionalDescription);
|
||||
case NOTIFICATION_PROCESSING_STOP:
|
||||
long frequencyToRemove = 0;
|
||||
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
|
||||
{
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
{
|
||||
frequencyToRemove = entry.getKey();
|
||||
removeCallEvents(entry.getKey());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(frequencyToRemove > 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case NOTIFICATION_PROCESSING_START_REJECTED:
|
||||
long frequencyToUpdate = 0;
|
||||
|
||||
if(frequencyToRemove > 0)
|
||||
{
|
||||
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToRemove);
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
|
||||
{
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
{
|
||||
frequencyToUpdate = entry.getKey();
|
||||
updateCallEventDetails(entry.getKey(), CHANNEL_START_REJECTED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Unlock the frequency in the channel rotation monitor
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove));
|
||||
}
|
||||
if(frequencyToUpdate > 0)
|
||||
{
|
||||
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToUpdate);
|
||||
|
||||
//Add the traffic channel back to the queue to be reused
|
||||
if(!mAvailableTrafficChannelQueue.contains(channel))
|
||||
{
|
||||
mAvailableTrafficChannelQueue.add(channel);
|
||||
//Unlock the frequency in the channel rotation monitor
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToUpdate));
|
||||
}
|
||||
|
||||
//Add the traffic channel back to the queue to be reused
|
||||
if(!mAvailableTrafficChannelQueue.contains(channel))
|
||||
{
|
||||
mAvailableTrafficChannelQueue.add(channel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.List;
|
||||
public class DecodeConfigDMR extends DecodeConfiguration
|
||||
{
|
||||
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 200;
|
||||
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
|
||||
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM_MS = 2000;
|
||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||
private boolean mIgnoreDataCalls = true;
|
||||
|
||||
@@ -88,7 +88,6 @@ public class DMRLogicalChannel extends DMRChannel
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("LSN:").append(getLogicalSlotNumber());
|
||||
sb.append(" LCN:").append(getValue());
|
||||
sb.append(" TS:").append(getTimeslot());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,6 @@ public enum Opcode
|
||||
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT(Vendor.MOTOROLA_CONNECT_PLUS, 28, "ENHANCED DATA REVERT WINDOW ANNOUNCEMENT"),
|
||||
MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"),
|
||||
|
||||
//TODO: Capacity Max (Tier III) also supports Enhanced Data Revert channels with windowing ... they either
|
||||
//TODO: use CSBKO 41 and 42, or they have a different pair of messages.
|
||||
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
|
||||
|
||||
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"),
|
||||
|
||||
@@ -34,7 +34,7 @@ import io.github.dsheirer.protocol.Protocol;
|
||||
public class DecodeEvent implements IDecodeEvent
|
||||
{
|
||||
private long mTimeStart;
|
||||
private long mDuration;
|
||||
private long mTimeEnd;
|
||||
private String mEventDescription;
|
||||
private IdentifierCollection mIdentifierCollection;
|
||||
private IChannelDescriptor mChannelDescriptor;
|
||||
@@ -66,7 +66,7 @@ public class DecodeEvent implements IDecodeEvent
|
||||
{
|
||||
if(timestamp > mTimeStart)
|
||||
{
|
||||
mDuration = timestamp - mTimeStart;
|
||||
mTimeEnd = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,13 +87,26 @@ public class DecodeEvent implements IDecodeEvent
|
||||
return mTimeStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event end in milliseconds
|
||||
*/
|
||||
public long getTimeEnd()
|
||||
{
|
||||
return mTimeEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event duration in milliseconds
|
||||
*/
|
||||
@Override
|
||||
public long getDuration()
|
||||
{
|
||||
return mDuration;
|
||||
if(mTimeEnd < mTimeStart)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mTimeEnd - mTimeStart;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +114,7 @@ public class DecodeEvent implements IDecodeEvent
|
||||
*/
|
||||
public void setDuration(long duration)
|
||||
{
|
||||
mDuration = duration;
|
||||
mTimeEnd = mTimeStart + duration;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,7 +242,7 @@ public class DecodeEvent implements IDecodeEvent
|
||||
{
|
||||
sb.append(" IDS:").append(Joiner.on(",").join(mIdentifierCollection.getIdentifiers()));
|
||||
}
|
||||
sb.append(" DURATION:").append(mDuration);
|
||||
sb.append(" DURATION:").append(getDuration());
|
||||
sb.append(" CHANNEL:").append(mChannelDescriptor);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -38,6 +38,12 @@ public interface IDecodeEvent
|
||||
*/
|
||||
long getTimeStart();
|
||||
|
||||
/**
|
||||
* Event end time
|
||||
* @return timestamp in milliseconds
|
||||
*/
|
||||
long getTimeEnd();
|
||||
|
||||
/**
|
||||
* Event duration.
|
||||
*
|
||||
|
||||
@@ -31,6 +31,7 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
public class DecodeConfigMPT1327 extends DecodeConfiguration implements WithCallTimeout
|
||||
{
|
||||
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 500;
|
||||
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
|
||||
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM_MS = 2000;
|
||||
|
||||
private String mChannelMapName;
|
||||
|
||||
@@ -30,7 +30,8 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
|
||||
public class DecodeConfigP25Phase1 extends DecodeConfiguration
|
||||
{
|
||||
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 500;
|
||||
public static final int CHANNEL_ROTATION_DELAY_MINIMUM_MS = 400;
|
||||
public static final int CHANNEL_ROTATION_DELAY_DEFAULT_MS = 500;
|
||||
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM_MS = 2000;
|
||||
|
||||
private P25P1Decoder.Modulation mModulation = P25P1Decoder.Modulation.C4FM;
|
||||
|
||||
Reference in New Issue
Block a user