#701 P25 Phase 2 Tone Metadata and Channel State (#702)

* Resolves #701 Updates P25P2 Tone metadata handling and P25P2 channel state management.
This commit is contained in:
Denny Sheirer
2020-03-28 09:51:11 +00:00
committed by GitHub
parent 41df207bb9
commit 158c8b4b8a
25 changed files with 493 additions and 611 deletions

View File

@@ -572,7 +572,7 @@ public class P25TrafficChannelManager extends Module implements IDecodeEventProv
if(type == null)
{
mLog.debug("Unrecognized opcode for determining decode event type: " + opcode.name());
mLog.error("Unrecognized opcode for determining decode event type: " + opcode.name());
type = DecodeEventType.CALL;
}
@@ -697,7 +697,7 @@ public class P25TrafficChannelManager extends Module implements IDecodeEventProv
//Issue a disable request for each traffic channel
for(Channel channel: channels)
{
mLog.debug("Stopping traffic channel: " + channel);
mLog.info("Stopping traffic channel: " + channel);
broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
}
}

View File

@@ -19,6 +19,7 @@
package io.github.dsheirer.module.decode.p25.audio;
import com.google.common.base.Joiner;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.audio.codec.mbe.AmbeAudioModule;
import io.github.dsheirer.audio.squelch.SquelchState;
@@ -27,9 +28,7 @@ import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
import io.github.dsheirer.identifier.tone.P25CallProgressIdentifier;
import io.github.dsheirer.identifier.tone.P25DtmfIdentifier;
import io.github.dsheirer.identifier.tone.P25KnoxIdentifier;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.tone.P25ToneIdentifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
@@ -51,17 +50,9 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
{
private final static Logger mLog = LoggerFactory.getLogger(P25P2AudioModule.class);
private static final String METADATA_TYPE_DTMF = "DTMF";
private static final String METADATA_TYPE_KNOX = "KNOX";
private static final String METADATA_TYPE_TONE = "TONE";
private static final String METADATA_TYPE_CALL_PROGRESS = "CALL PROGRESS";
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private MetadataProcessor mDtmfMetadataProcessor;
private MetadataProcessor mKnoxMetadataProcessor;
private MetadataProcessor mToneMetadataProcessor;
private MetadataProcessor mCallProcessMetadataProcessor;
private ToneMetadataProcessor mToneMetadataProcessor = new ToneMetadataProcessor();
private int mTimeslot;
private Queue<AbstractVoiceTimeslot> mQueuedAudioTimeslots = new ArrayDeque<>();
private boolean mEncryptedCallStateEstablished = false;
@@ -92,10 +83,10 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
@Override
public void reset()
{
mCallProcessMetadataProcessor = null;
mDtmfMetadataProcessor = null;
mKnoxMetadataProcessor = null;
mToneMetadataProcessor = null;
//Explicitly clear FROM identifiers to ensure previous call TONE identifiers are cleared.
mIdentifierCollection.remove(Role.FROM);
mToneMetadataProcessor.reset();
mQueuedAudioTimeslots.clear();
//Reset encrypted call handling flags
@@ -179,107 +170,44 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
{
byte[] voiceFrameBytes = voiceFrame.getBytes();
IAudioWithMetadata audioWithMetadata = getAudioCodec().getAudioWithMetadata(voiceFrameBytes);
processMetadata(audioWithMetadata);
addAudio(audioWithMetadata.getAudio());
try
{
IAudioWithMetadata audioWithMetadata = getAudioCodec().getAudioWithMetadata(voiceFrameBytes);
addAudio(audioWithMetadata.getAudio());
processMetadata(audioWithMetadata);
}
catch(Exception e)
{
mLog.error("Error synthesizing AMBE audio - continuing [" + e.getLocalizedMessage() + "]");
}
}
}
}
/**
* Processes optional metadata that can be included with decoded audio (ie dtmf, tones, knox, etc.)
* Processes optional metadata that can be included with decoded audio (ie dtmf, tones, knox, etc.) so that the
* tone metadata can be converted into a FROM identifier and included with any call segment.
*/
private void processMetadata(IAudioWithMetadata audioWithMetadata)
{
if(mIdentifierUpdateNotificationListener != null)
if(audioWithMetadata.hasMetadata() && mIdentifierUpdateNotificationListener != null)
{
if(audioWithMetadata.hasMetadata())
for(Map.Entry<String,String> entry: audioWithMetadata.getMetadata().entrySet())
{
Map<String,String> metadata = audioWithMetadata.getMetadata();
//Each metadata map entry contains a tone-type (key) and tone (value)
Identifier metadataIdentifier = mToneMetadataProcessor.process(entry.getKey(), entry.getValue());
if(metadata.containsKey(METADATA_TYPE_DTMF))
if(metadataIdentifier != null)
{
String dtmf = metadata.get(METADATA_TYPE_DTMF);
if(dtmf != null)
{
if(mDtmfMetadataProcessor == null)
{
mDtmfMetadataProcessor = new MetadataProcessor();
}
List<String> dtmfTones = mDtmfMetadataProcessor.process(dtmf);
broadcast(P25DtmfIdentifier.create(dtmfTones));
}
}
else if(metadata.containsKey(METADATA_TYPE_KNOX))
{
String knox = metadata.get(METADATA_TYPE_KNOX);
if(knox != null)
{
if(mKnoxMetadataProcessor == null)
{
mKnoxMetadataProcessor = new MetadataProcessor();
}
List<String> knoxTones = mKnoxMetadataProcessor.process(knox);
broadcast(P25KnoxIdentifier.create(knoxTones));
}
}
else if(metadata.containsKey(METADATA_TYPE_TONE))
{
String tone = metadata.get(METADATA_TYPE_TONE);
if(tone != null)
{
if(mToneMetadataProcessor == null)
{
mToneMetadataProcessor = new MetadataProcessor();
}
List<String> tones = mToneMetadataProcessor.process(tone);
broadcast(P25ToneIdentifier.create(tones));
}
}
else if(metadata.containsKey(METADATA_TYPE_CALL_PROGRESS))
{
String callProgressTone = metadata.get(METADATA_TYPE_CALL_PROGRESS);
if(callProgressTone != null)
{
if(mCallProcessMetadataProcessor == null)
{
mCallProcessMetadataProcessor = new MetadataProcessor();
}
List<String> tones = mCallProcessMetadataProcessor.process(callProgressTone);
broadcast(P25CallProgressIdentifier.create(tones));
}
}
}
else
{
if(mDtmfMetadataProcessor != null)
{
mDtmfMetadataProcessor.noMetadata();
}
if(mKnoxMetadataProcessor != null)
{
mKnoxMetadataProcessor.noMetadata();
}
if(mCallProcessMetadataProcessor != null)
{
mCallProcessMetadataProcessor.noMetadata();
}
if(mToneMetadataProcessor != null)
{
mToneMetadataProcessor.noMetadata();
broadcast(metadataIdentifier);
}
}
}
}
/**
* Broadcasts the identifier to a registered listener
*/
private void broadcast(Identifier identifier)
{
if(mIdentifierUpdateNotificationListener != null)
@@ -289,12 +217,18 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
}
}
/**
* Registers the listener to receive identifier updates
*/
@Override
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
{
mIdentifierUpdateNotificationListener = listener;
}
/**
* Unregisters a listener from receiving identifier updates
*/
@Override
public void removeIdentifierUpdateListener()
{
@@ -302,40 +236,124 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
}
/**
* Processes metadata string values to provide a de-duplicated list of metadata values.
*
* This is necessary for metadata such as DTMF where a tone can span multiple 20ms voice frames but is intended
* to be represented as a single tone. An empty (no-metadata) frame causes the repeat checker to stop and allow
* the next metadata value to be added to the list.
* Process AMBE audio frame tone metadata. Tracks the count of sequential frames containing tone metadata to
* provide a list of each unique tone and a time duration (milliseconds) for the tone. Tones are concatenated into
* a comma separated list and included as call segment metadata.
*/
public class MetadataProcessor
public class ToneMetadataProcessor
{
private List<String> mValues = new ArrayList<>();
private String mLastInput;
private List<ToneMetadata> mToneMetadata = new ArrayList<>();
public List<String> process(String value)
/**
* Resets or clears any accumulated call tone metadata to prepare for the next call.
*/
public void reset()
{
if(value != null)
{
if(mLastInput != null && mLastInput.equalsIgnoreCase(value))
{
//Suppress the repeat and return the current value
return mValues;
}
else
{
mLastInput = value;
mValues.add(value);
return mValues;
}
}
return mValues;
mToneMetadata.clear();
}
public void noMetadata()
/**
* Process the tone metadata
* @param type of tone
* @param value of tone
* @return an identifier with the accumulated tone metadata set
*/
public Identifier process(String type, String value)
{
mLastInput = null;
if(type == null || value == null)
{
return null;
}
ToneMetadata toneMetadata = (mToneMetadata.size() > 0 ? mToneMetadata.get(mToneMetadata.size() - 1) : null);
if(toneMetadata == null || !toneMetadata.matches(type, value))
{
toneMetadata = new ToneMetadata(type, value);
mToneMetadata.add(toneMetadata);
}
toneMetadata.incrementCount();
return P25ToneIdentifier.create(Joiner.on(",").join(mToneMetadata));
}
}
/**
* Metadata about the tone type being transmitted and the duration.
*
* Note: each AMBE frame is 20 milliseconds in duration, so total duration is the count of 20 ms tone frames
* of the same metadata type.
*/
public class ToneMetadata
{
private String mType;
private String mValue;
private int mCount;
/**
* Constructs an instance
* @param type of tone
* @param value of the tone
*/
public ToneMetadata(String type, String value)
{
mType = type;
mValue = value;
mCount = 0;
}
/**
* Indicates if this tone metadata matches the tone metadata represented by the arguments
* @param type of tone
* @param value of tone
* @return true if they match
*/
public boolean matches(String type, String value)
{
return type != null && value != null && type.matches(mType) && value.matches(mValue);
}
/**
* Type of tone
*/
public String getType()
{
return mType;
}
/**
* Value of tone
*/
public String getValue()
{
return mValue;
}
/**
* Number of times this tone has occurred sequentially
*/
public int getCount()
{
return mCount;
}
/**
* Increments the count of the number of sequential frames that contained the tone
*/
public void incrementCount()
{
mCount++;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(mType);
sb.append(" ").append(mValue);
sb.append(" (").append(mCount * 20).append("ms)");
return sb.toString();
}
}
@@ -359,5 +377,4 @@ public class P25P2AudioModule extends AmbeAudioModule implements IdentifierUpdat
}
}
}
}

View File

@@ -43,6 +43,7 @@ import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.p25.P25DecodeEvent;
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel;
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacPduType;
@@ -102,6 +103,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
private PatchGroupManager mPatchGroupManager = new PatchGroupManager();
private P25P2NetworkConfigurationMonitor mNetworkConfigurationMonitor = new P25P2NetworkConfigurationMonitor();
private DecodeEvent mCurrentCallEvent;
private int mEndPttOnFacchCounter = 0;
/**
* Constructs an APCO-25 decoder state for a traffic channel.
@@ -138,7 +140,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
protected void resetState()
{
super.resetState();
closeCurrentCallEvent(System.currentTimeMillis(), true);
closeCurrentCallEvent(System.currentTimeMillis(), true, MacPduType.MAC_3_IDLE);
mEndPttOnFacchCounter = 0;
}
/**
@@ -151,28 +154,68 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
{
if(message instanceof MacMessage)
{
processMacMessage((MacMessage)message);
MacMessage macMessage = (MacMessage)message;
processMacMessage(macMessage);
MacPduType macPduType = macMessage.getMacPduType();
//Ignore End PTT - this is handled in the processMacMessage() method
if(macPduType != MacPduType.MAC_2_END_PTT)
{
mEndPttOnFacchCounter = 0;
continueState(getStateFromPduType(macPduType));
}
if(macPduType == MacPduType.MAC_3_IDLE)
{
closeCurrentCallEvent(message.getTimestamp(), true, macPduType);
}
}
else if(message instanceof AbstractVoiceTimeslot)
{
if(isEncrypted())
if(mCurrentCallEvent != null)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED, getTimeslot()));
}
else
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
if(isEncrypted())
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED, getTimeslot()));
}
else
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
}
}
updateCurrentCall(null, null, message.getTimestamp());
}
else if(message instanceof EncryptionSynchronizationSequence)
{
//We don't send any state events for this message since it can only occur in conjunction with
//an audio frame that already sends the call state event
getIdentifierCollection().update(message.getIdentifiers());
}
}
}
/**
* Updates the channel state according to the PDU type
*/
private static State getStateFromPduType(MacPduType macPduType)
{
switch(macPduType)
{
case MAC_1_PTT:
return State.CALL;
case MAC_2_END_PTT:
return State.TEARDOWN;
case MAC_4_ACTIVE:
case MAC_6_HANGTIME:
return State.ACTIVE;
case MAC_3_IDLE:
default:
return State.IDLE;
}
}
/**
* Adds the current channel to the local identifier collection which will cause it to be broadcast to all of the
* other listeners and will allow both timeslots on this channel to receive it and update accordingly.
@@ -215,30 +258,67 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
{
//Add the set of identifiers before we close out the call event to ensure they're captured in
//the closing event.
for(Identifier identifier : mac.getIdentifiers())
if(mCurrentCallEvent != null)
{
if(identifier.getRole() == Role.FROM && identifier instanceof APCO25RadioIdentifier)
for(Identifier identifier : mac.getIdentifiers())
{
int value = ((APCO25RadioIdentifier)identifier).getValue();
if(value == SYSTEM_CONTROLLER)
if(identifier.getRole() == Role.FROM)
{
//ignore so we don't overwrite the real from radio identifier
if(identifier instanceof APCO25RadioIdentifier)
{
int value = ((APCO25RadioIdentifier)identifier).getValue();
//Group call End PTT uses a FROM value of 0xFFFFFF - don't overwrite the correct id
if(value != SYSTEM_CONTROLLER)
{
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
}
}
}
else if(identifier.getRole() == Role.TO)
{
if(identifier instanceof APCO25Talkgroup)
{
int value = ((APCO25Talkgroup)identifier).getValue();
//Individual call End PTT uses a TO value of 0 - don't overwrite the correct id
if(value != 0)
{
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
}
}
}
else
{
//Add to the identifier collection after filtering through the patch group manager
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
}
}
else
{
//Add to the identifier collection after filtering through the patch group manager
getIdentifierCollection().update(mPatchGroupManager.update(identifier));
}
}
closeCurrentCallEvent(message.getTimestamp(), true);
if(message.getDataUnitID().isFACCH())
{
mEndPttOnFacchCounter++;
//FNE sending 2 or more End PTT in FACCH timeslots indicates a channel teardown event.
if(mEndPttOnFacchCounter > 1)
{
if(mChannelType == ChannelType.TRAFFIC)
{
closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_2_END_PTT);
//Don't send a continue state here ... the close() method will send the TEARDOWN state
}
//Otherwise the channel state is set to ACTIVE in anticipation of further call activity
else
{
closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_4_ACTIVE);
continueState(State.RESET);
}
}
}
else
{
closeCurrentCallEvent(message.getTimestamp(), true, MacPduType.MAC_4_ACTIVE);
}
}
break;
case TDMA_0_NULL_INFORMATION_MESSAGE:
@@ -246,15 +326,13 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
if(type == MacPduType.MAC_3_IDLE || type == MacPduType.MAC_6_HANGTIME)
{
closeCurrentCallEvent(message.getTimestamp(), true);
closeCurrentCallEvent(message.getTimestamp(), true, type);
}
continueState(State.ACTIVE);
break;
case TDMA_1_GROUP_VOICE_CHANNEL_USER_ABBREVIATED:
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
{
closeCurrentCallEvent(message.getTimestamp(), false);
getIdentifierCollection().remove(Role.FROM);
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
for(Identifier identifier : mac.getIdentifiers())
{
@@ -291,8 +369,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
case TDMA_33_GROUP_VOICE_CHANNEL_USER_EXTENDED:
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
{
closeCurrentCallEvent(message.getTimestamp(), false);
getIdentifierCollection().remove(Role.FROM);
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
for(Identifier identifier : mac.getIdentifiers())
{
@@ -329,8 +406,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
case TDMA_2_UNIT_TO_UNIT_VOICE_CHANNEL_USER:
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
{
closeCurrentCallEvent(message.getTimestamp(), false);
getIdentifierCollection().remove(Role.FROM);
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
for(Identifier identifier : mac.getIdentifiers())
{
@@ -367,8 +443,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
case TDMA_34_UNIT_TO_UNIT_VOICE_CHANNEL_USER_EXTENDED:
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
{
closeCurrentCallEvent(message.getTimestamp(), false);
getIdentifierCollection().remove(Role.FROM);
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
for(Identifier identifier : mac.getIdentifiers())
{
@@ -405,8 +480,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
case TDMA_3_TELEPHONE_INTERCONNECT_VOICE_CHANNEL_USER:
if(message.getMacPduType() == MacPduType.MAC_6_HANGTIME)
{
closeCurrentCallEvent(message.getTimestamp(), false);
getIdentifierCollection().remove(Role.FROM);
closeCurrentCallEvent(message.getTimestamp(), false, MacPduType.MAC_6_HANGTIME);
for(Identifier identifier : mac.getIdentifiers())
{
@@ -439,7 +513,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
}
}
}
continueState(State.ACTIVE);
break;
case TDMA_5_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE:
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultiple)
@@ -461,7 +534,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
broadcastCurrentChannel(gvcgum.getChannelC());
}
}
continueState(State.ACTIVE);
break;
case TDMA_37_GROUP_VOICE_CHANNEL_GRANT_UPDATE_MULTIPLE_EXPLICIT:
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateMultipleExplicit)
@@ -478,7 +550,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
broadcastCurrentChannel(gvcgume.getChannelB());
}
}
continueState(State.ACTIVE);
break;
case PHASE1_64_GROUP_VOICE_CHANNEL_GRANT_ABBREVIATED:
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantAbbreviated)
@@ -490,7 +561,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
broadcastCurrentChannel(gvcga.getChannel());
}
}
continueState(State.ACTIVE);
break;
case PHASE1_66_GROUP_VOICE_CHANNEL_GRANT_UPDATE:
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdate)
@@ -507,7 +577,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
broadcastCurrentChannel(gvcgu.getChannelB());
}
}
continueState(State.ACTIVE);
break;
case PHASE1_70_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_ABBREVIATED:
if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantAbbreviated)
@@ -519,7 +588,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
broadcastCurrentChannel(uuvcga.getChannel());
}
}
continueState(State.ACTIVE);
break;
case PHASE1_195_GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT:
if(getCurrentChannel() == null && mac instanceof GroupVoiceChannelGrantUpdateExplicit)
@@ -531,7 +599,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
broadcastCurrentChannel(gvcgue.getChannel());
}
}
continueState(State.ACTIVE);
break;
case PHASE1_198_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE_EXTENDED:
if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantUpdateExtended)
@@ -543,7 +610,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
broadcastCurrentChannel(uuvcgue.getChannel());
}
}
continueState(State.ACTIVE);
break;
case TDMA_17_INDIRECT_GROUP_PAGING:
MutableIdentifierCollection icGroupPaging = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
@@ -556,7 +622,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.details("GROUP PAGE")
.identifiers(icGroupPaging)
.build());
continueState(State.ACTIVE);
break;
case TDMA_18_INDIVIDUAL_PAGING_MESSAGE_WITH_PRIORITY:
if(mac instanceof IndividualPagingMessage)
@@ -575,7 +640,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icIndividualPaging)
.build());
}
continueState(State.ACTIVE);
break;
case TDMA_48_POWER_CONTROL_SIGNAL_QUALITY:
if(mac instanceof PowerControlSignalQuality)
@@ -593,7 +657,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icPowerControl)
.build());
}
continueState(State.ACTIVE);
break;
case TDMA_49_MAC_RELEASE:
if(mac instanceof MacRelease)
@@ -604,7 +667,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
icMacRelease.remove(IdentifierClass.USER);
icMacRelease.update(mac.getIdentifiers());
closeCurrentCallEvent(message.getTimestamp(), true);
closeCurrentCallEvent(message.getTimestamp(), true, message.getMacPduType());
broadcast(P25DecodeEvent.builder(message.getTimestamp())
.channel(getCurrentChannel())
@@ -630,7 +693,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icMacRelease)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_68_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_ABBREVIATED:
if(mac instanceof UnitToUnitVoiceChannelGrantAbbreviated)
@@ -647,7 +709,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icGrant)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_69_UNIT_TO_UNIT_ANSWER_REQUEST_ABBREVIATED:
if(mac instanceof UnitToUnitAnswerRequestAbbreviated)
@@ -664,7 +725,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icRequest)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_74_TELEPHONE_INTERCONNECT_ANSWER_REQUEST:
if(mac instanceof TelephoneInterconnectAnswerRequest)
@@ -681,7 +741,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icRequest)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_76_RADIO_UNIT_MONITOR_COMMAND_ABBREVIATED:
if(mac instanceof RadioUnitMonitorCommand)
@@ -699,7 +758,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icRequest)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_84_SNDCP_DATA_CHANNEL_GRANT:
if(mac instanceof SNDCPDataChannelGrant)
@@ -716,7 +774,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icGrant)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_85_SNDCP_DATA_PAGE_REQUEST:
if(mac instanceof SNDCPDataPageRequest)
@@ -733,7 +790,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icPage)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_88_STATUS_UPDATE_ABBREVIATED:
if(mac instanceof StatusUpdateAbbreviated)
@@ -750,7 +806,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icStatusUpdate)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_90_STATUS_QUERY_ABBREVIATED:
if(mac instanceof StatusQueryAbbreviated)
@@ -766,7 +821,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icStatusUpdate)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_92_MESSAGE_UPDATE_ABBREVIATED:
if(mac instanceof MessageUpdateAbbreviated)
@@ -783,7 +837,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icStatusUpdate)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_94_RADIO_UNIT_MONITOR_COMMAND_ENHANCED:
if(mac instanceof RadioUnitMonitorCommandEnhanced)
@@ -802,7 +855,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icRequest)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_95_CALL_ALERT_ABBREVIATED:
MutableIdentifierCollection icCallAlert = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
@@ -814,7 +866,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.eventDescription(DecodeEventType.CALL_ALERT.toString())
.identifiers(icCallAlert)
.build());
continueState(State.ACTIVE);
break;
case PHASE1_96_ACK_RESPONSE:
if(mac instanceof AcknowledgeResponse)
@@ -831,7 +882,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icAckResponse)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_97_QUEUED_RESPONSE:
if(mac instanceof QueuedResponse)
@@ -850,7 +900,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icQueuedResponse)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_100_EXTENDED_FUNCTION_COMMAND_ABBREVIATED:
if(mac instanceof ExtendedFunctionCommand)
@@ -868,7 +917,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icExtendedFunction)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_103_DENY_RESPONSE:
if(mac instanceof DenyResponse)
@@ -885,7 +933,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icDenyResponse)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_106_GROUP_AFFILIATION_QUERY_ABBREVIATED:
MutableIdentifierCollection icGroupAffiliationQuery = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
@@ -898,7 +945,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.details("GROUP AFFILIATION")
.identifiers(icGroupAffiliationQuery)
.build());
continueState(State.ACTIVE);
break;
case PHASE1_109_UNIT_REGISTRATION_COMMAND_ABBREVIATED:
MutableIdentifierCollection icUnitRegisterCommand = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
@@ -911,7 +957,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.details("UNIT REGISTRATION")
.identifiers(icUnitRegisterCommand)
.build());
continueState(State.ACTIVE);
break;
case PHASE1_197_UNIT_TO_UNIT_ANSWER_REQUEST_EXTENDED:
if(mac instanceof UnitToUnitAnswerRequestExtended)
@@ -930,7 +975,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icUnitAnswerRequestExtended)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_204_RADIO_UNIT_MONITOR_COMMAND_EXTENDED:
if(mac instanceof RadioUnitMonitorCommandExtended)
@@ -948,7 +992,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icRequest)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_216_STATUS_UPDATE_EXTENDED:
if(mac instanceof StatusUpdateExtended)
@@ -965,7 +1008,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icStatusUpdate)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_218_STATUS_QUERY_EXTENDED:
MutableIdentifierCollection icStatusQueryExtended = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
@@ -978,7 +1020,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.details("STATUS QUERY")
.identifiers(icStatusQueryExtended)
.build());
continueState(State.ACTIVE);
break;
case PHASE1_220_MESSAGE_UPDATE_EXTENDED:
if(mac instanceof MessageUpdateExtended)
@@ -995,7 +1036,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icStatusUpdate)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_223_CALL_ALERT_EXTENDED:
if(mac instanceof CallAlertExtended)
@@ -1010,7 +1050,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icCallAlertExtended)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_228_EXTENDED_FUNCTION_COMMAND_EXTENDED:
if(mac instanceof ExtendedFunctionCommandExtended)
@@ -1028,7 +1067,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icExtendedFunction)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_234_GROUP_AFFILIATION_QUERY_EXTENDED:
if(mac instanceof GroupAffiliationQueryExtended)
@@ -1045,7 +1083,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.identifiers(icGroupAffiliationQueryExtended)
.build());
}
continueState(State.ACTIVE);
break;
case PHASE1_115_IDENTIFIER_UPDATE_TDMA:
@@ -1071,8 +1108,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
case PHASE1_196_UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_EXTENDED:
case UNKNOWN:
default:
continueState(State.ACTIVE);
//ignore
break;
}
}
@@ -1135,7 +1170,15 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
.build();
broadcast(mCurrentCallEvent);
broadcast(new DecoderStateEvent(this, Event.START, State.CALL, getTimeslot()));
if(isEncrypted())
{
broadcast(new DecoderStateEvent(this, Event.START, State.ENCRYPTED, getTimeslot()));
}
else
{
broadcast(new DecoderStateEvent(this, Event.START, State.CALL, getTimeslot()));
}
}
else
{
@@ -1152,7 +1195,6 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
mCurrentCallEvent.end(timestamp);
broadcast(mCurrentCallEvent);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
}
}
@@ -1160,8 +1202,10 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
* Ends/closes the current call event.
*
* @param timestamp of the message that indicates the event has ended.
* @param resetIdentifiers to reset the FROM/TO identifiers
* @param pduType of the message that caused the close call event - to determine channel state after call
*/
private void closeCurrentCallEvent(long timestamp, boolean resetIdentifiers)
private void closeCurrentCallEvent(long timestamp, boolean resetIdentifiers, MacPduType pduType)
{
if(mCurrentCallEvent != null)
{
@@ -1169,21 +1213,21 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
broadcast(mCurrentCallEvent);
mCurrentCallEvent = null;
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
}
else
{
continueState(State.ACTIVE);
}
broadcast(new DecoderStateEvent(this, Event.END, getStateFromPduType(pduType), getTimeslot()));
if(resetIdentifiers)
{
getIdentifierCollection().remove(IdentifierClass.USER);
if(resetIdentifiers)
{
getIdentifierCollection().remove(IdentifierClass.USER);
}
else
{
//Only clear the from identifier at this point ... the channel may still be allocated to the TO talkgroup
getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM);
}
}
else
else if(pduType == MacPduType.MAC_2_END_PTT)
{
//Only clear the from identifier at this point ... the channel may still be allocated to the TO talkgroup
getIdentifierCollection().remove(IdentifierClass.USER, Role.FROM);
broadcast(new DecoderStateEvent(this, Event.END, getStateFromPduType(pduType), getTimeslot()));
}
}
@@ -1215,14 +1259,17 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{
switch(event.getEvent())
if(event.getTimeslot() == getTimeslot())
{
case RESET:
resetState();
mNetworkConfigurationMonitor.reset();
break;
default:
break;
switch(event.getEvent())
{
case RESET:
resetState();
mNetworkConfigurationMonitor.reset();
break;
default:
break;
}
}
}

View File

@@ -224,25 +224,30 @@ public class P25P2MessageFramer implements Listener<Dibit>
public static void main(String[] args)
{
UserPreferences userPreferences = new UserPreferences();
userPreferences.getJmbeLibraryPreference().setPathJmbeLibrary(Path.of("/home/denny/JMBE/jmbe-1.0.2.jar"));
userPreferences.getJmbeLibraryPreference().setPathJmbeLibrary(Path.of("/home/denny/JMBE/jmbe-1.0.3.jar"));
ApplicationLog applicationLog = new ApplicationLog(userPreferences);
applicationLog.start();
// Path directory = Paths.get("/home/denny/SDRTrunk/recordings");
Path directory = Paths.get("/home/denny/temp/Harris P25-2 Logs/bits");
// Path directory = Paths.get("/media/denny/500G1EXT4/PBITRecordings");
// ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 1186, 1189); //Nugent
ScrambleParameters scrambleParameters = new ScrambleParameters(123654, 813, 10); //Hills
// ScrambleParameters scrambleParameters = new ScrambleParameters(1, 972, 972));
// ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 686, 677)); //CNYICC - Rome
// ScrambleParameters scrambleParameters = new ScrambleParameters(781824, 686, 677); //CNYICC - Rome
// Channel channel = new Channel("Phase 2 Test", Channel.ChannelType.STANDARD);
Channel channel = new Channel("Phase 2 Test", Channel.ChannelType.TRAFFIC);
Channel channel = new Channel("Phase 2 Test");
channel.setDecodeConfiguration(new DecodeConfigP25Phase2());
AliasList aliasList = new AliasList("Test Alias List");
Alias alias = new Alias("TG Range 1-65535");
alias.addAliasID(new Record());
// Alias alias = new Alias("TG Range 1-65535");
// alias.addAliasID(new Record());
// alias.addAliasID(new TalkgroupRange(Protocol.APCO25, 1, 65535));
alias.addAliasID(new Talkgroup(Protocol.APCO25, 11185));
aliasList.addAlias(alias);
// alias.addAliasID(new Talkgroup(Protocol.APCO25, 11857));
// alias.addAliasID(new Talkgroup(Protocol.APCO25, 12601));
// aliasList.addAlias(alias);
AudioRecordingManager recordingManager = new AudioRecordingManager(userPreferences);
recordingManager.start();
ProcessingChain processingChain = new ProcessingChain(channel, new AliasModel());
@@ -254,20 +259,21 @@ public class P25P2MessageFramer implements Listener<Dibit>
processingChain.addModule(new P25P2AudioModule(userPreferences, 1, aliasList));
MessageProviderModule messageProviderModule = new MessageProviderModule();
processingChain.addModule(messageProviderModule);
processingChain.start();
try(OutputStream logOutput = Files.newOutputStream(directory.resolve("log.txt")))
{
try
{
DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*PHASE2_Hills*TRAFFIC.bits");
// DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*042102*TRAFFIC.bits");
// DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*042102*.bits");
DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*214601*Hills*TRAFFIC.bits");
stream.forEach(new Consumer<Path>()
{
@Override
public void accept(Path path)
{
mLog.debug("Processing:" + path.toString());
mLog.debug("\n\nProcessing:" + path.toString() + "\n\n");
try
{
logOutput.write(("Processing:" + path.toString() + "\n").getBytes());
@@ -277,6 +283,8 @@ public class P25P2MessageFramer implements Listener<Dibit>
mLog.error("Error", ioe);
}
processingChain.start();
P25P2MessageFramer messageFramer = new P25P2MessageFramer(null, DecoderType.P25_PHASE2.getProtocol().getBitRate());
messageFramer.setScrambleParameters(scrambleParameters);
P25P2MessageProcessor messageProcessor = new P25P2MessageProcessor();
@@ -313,6 +321,12 @@ public class P25P2MessageFramer implements Listener<Dibit>
{
ioe.printStackTrace();
}
mLog.debug("**STOPPING PROCESSING CHAIN**");
processingChain.stop();
mLog.debug("\n============================================================================================================================\n");
}
}
);
@@ -326,5 +340,7 @@ public class P25P2MessageFramer implements Listener<Dibit>
{
mLog.error("Error", ioe);
}
recordingManager.stop();
}
}

View File

@@ -65,6 +65,22 @@ public enum DataUnitID
return mValueWithParity;
}
/**
* Indicates if this DUID is a SACCH, either scrambled or unscrambled
*/
public boolean isSACCH()
{
return this == SCRAMBLED_SACCH || this == UNSCRAMBLED_SACCH;
}
/**
* Indicates if this DUID is a FACCH, either scrambled or unscrambled
*/
public boolean isFACCH()
{
return this == SCRAMBLED_FACCH || this == UNSCRAMBLED_FACCH;
}
@Override
public String toString()
{