#959 Updates DMR decode event tracking and Con+ channel grant management. (#978)

Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-10-25 04:51:36 -04:00
committed by GitHub
parent fe714c69a7
commit de8cfa24f9
15 changed files with 408 additions and 237 deletions

View File

@@ -165,34 +165,25 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
{
PolyphaseChannelSource channelSource = null;
try
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
if(outputProcessor != null)
{
List<Integer> polyphaseIndexes = mChannelCalculator.getChannelIndexes(tunerChannel);
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
IPolyphaseChannelOutputProcessor outputProcessor = getOutputProcessor(polyphaseIndexes);
if(outputProcessor != null)
try
{
long centerFrequency = mChannelCalculator.getCenterFrequencyForIndexes(polyphaseIndexes);
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
try
{
channelSource = new PolyphaseChannelSource(tunerChannel, outputProcessor, mChannelSourceEventListener,
mChannelCalculator.getChannelSampleRate(), centerFrequency, channelSpecification);
mChannelSources.add(channelSource);
}
catch(FilterDesignException fde)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
}
mChannelSources.add(channelSource);
}
catch(FilterDesignException fde)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
}
}
catch(IllegalArgumentException iae)
{
mLog.info("Can't provide DDC for " + tunerChannel.toString() + " due to channelizer frequency [" +
mChannelCalculator.getCenterFrequency() + "] and sample rate [" +
(mChannelCalculator.getChannelCount() * mChannelCalculator.getChannelBandwidth()) + "]");
}
return channelSource;

View File

@@ -233,7 +233,8 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_MAXIMUM_MS);
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
DecodeConfigDMR.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -197,7 +197,8 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_MAXIMUM_MS);
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
DecodeConfigMPT1327.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -193,7 +193,8 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(),
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_MINIMUM_MS,
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_MAXIMUM_MS);
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_MAXIMUM_MS,
DecodeConfigP25Phase1.CHANNEL_ROTATION_DELAY_DEFAULT_MS);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()

View File

@@ -66,6 +66,7 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
private VBox mFrequencyBoxContainer;
private Spinner<Integer> mChannelRotationDelaySpinner;
private boolean mAllowMultipleFrequencies = false;
private int mFrequencyRotationDefault = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_DEFAULT;
private int mFrequencyRotationMinimum = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MINIMUM;
private int mFrequencyRotationMaximum = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MAXIMUM;
@@ -78,12 +79,13 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
* @param minRotationDelay in milliseconds for channel/frequency dwell
* @param maxRotationDelay in milliseconds for channel/frequency dwell
*/
public FrequencyEditor(TunerModel tunerModel, int minRotationDelay, int maxRotationDelay)
public FrequencyEditor(TunerModel tunerModel, int minRotationDelay, int maxRotationDelay, int defaultRotationDelay)
{
mTunerModel = tunerModel;
mAllowMultipleFrequencies = true;
mFrequencyRotationMinimum = minRotationDelay;
mFrequencyRotationMaximum = maxRotationDelay;
mFrequencyRotationDefault = defaultRotationDelay;
init();
}

View File

@@ -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);
}
/**

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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"),

View File

@@ -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();
}

View File

@@ -38,6 +38,12 @@ public interface IDecodeEvent
*/
long getTimeStart();
/**
* Event end time
* @return timestamp in milliseconds
*/
long getTimeEnd();
/**
* Event duration.
*

View File

@@ -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;

View File

@@ -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;

View File

@@ -46,6 +46,7 @@ public class ChannelRotationMonitor extends Module implements ISourceEventProvid
Listener<DecoderStateEvent>
{
public static final int CHANNEL_ROTATION_DELAY_MINIMUM = 200;
public static final int CHANNEL_ROTATION_DELAY_DEFAULT = 500;
public static final int CHANNEL_ROTATION_DELAY_MAXIMUM = 2000;
private final static Logger mLog = LoggerFactory.getLogger(ChannelRotationMonitor.class);