Skip to content

Commit

Permalink
#2089 Adds Motorola P25P2 TDMA data channel support
Browse files Browse the repository at this point in the history
  • Loading branch information
Dennis Sheirer committed Nov 11, 2024
1 parent 552d3a6 commit d54ef13
Show file tree
Hide file tree
Showing 18 changed files with 729 additions and 6 deletions.
21 changes: 21 additions & 0 deletions src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ public class P25P2Viewer extends VBox
private static final Logger mLog = LoggerFactory.getLogger(P25P2Viewer.class);
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p2";
private static final String LAST_WACN_VALUE = "last.wacn.value.p25p2";
private static final String LAST_SYSTEM_VALUE = "last.system.value.p25p2";
private static final String LAST_NAC_VALUE = "last.nac.value.p25p2";
private static final String FILE_FREQUENCY_REGEX = ".*\\d{8}_\\d{6}_(\\d{9}).*";
private Preferences mPreferences = Preferences.userNodeForPackage(P25P2Viewer.class);
private Button mSelectFileButton;
Expand Down Expand Up @@ -415,6 +418,12 @@ private IntegerTextField getWACNTextField()
if(mWACNTextField == null)
{
mWACNTextField = new IntegerTextField();
mWACNTextField.textProperty().addListener((ob, ol, ne) -> mPreferences.putInt(LAST_WACN_VALUE, getWACNTextField().get()));
int previous = mPreferences.getInt(LAST_WACN_VALUE, 0);
if(previous > 0)
{
getWACNTextField().set(previous);
}
}

return mWACNTextField;
Expand All @@ -425,6 +434,12 @@ private IntegerTextField getSystemTextField()
if(mSystemTextField == null)
{
mSystemTextField = new IntegerTextField();
mSystemTextField.textProperty().addListener((ob, ol, ne) -> mPreferences.putInt(LAST_SYSTEM_VALUE, getSystemTextField().get()));
int previous = mPreferences.getInt(LAST_SYSTEM_VALUE, 0);
if(previous > 0)
{
getSystemTextField().set(previous);
}
}

return mSystemTextField;
Expand All @@ -435,6 +450,12 @@ private IntegerTextField getNACTextField()
if(mNACTextField == null)
{
mNACTextField = new IntegerTextField();
mNACTextField.textProperty().addListener((ob, ol, ne) -> mPreferences.putInt(LAST_NAC_VALUE, getNACTextField().get()));
int previous = mPreferences.getInt(LAST_NAC_VALUE, 0);
if(previous > 0)
{
getNACTextField().set(previous);
}
}

return mNACTextField;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class P25TrafficChannelEventTracker
{
private static final Logger LOGGER = LoggerFactory.getLogger(P25TrafficChannelEventTracker.class);
private static final long STALE_EVENT_THRESHOLD_MS = 2000;
private static final long MAX_TDMA_DATA_CHANNEL_EVENT_DURATION_MS = 15000;
private P25ChannelGrantEvent mEvent;
private boolean mStarted = false;
private boolean mComplete = false;
Expand Down Expand Up @@ -74,6 +75,14 @@ public boolean isStale(long timestamp)
return timestamp - getEvent().getTimeStart() > STALE_EVENT_THRESHOLD_MS;
}

/**
* Indicates if the TDMA data channel duration exceeds the threshold (15 seconds)
*/
public boolean exceedsMaxTDMADataDuration()
{
return getEvent().getDuration() > MAX_TDMA_DATA_CHANNEL_EVENT_DURATION_MS;
}

/**
* Adds the identifier to the tracked event if the event's identifier collection does not already have it.
* @param identifier to add
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode;
import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions;
import io.github.dsheirer.module.decode.p25.reference.ServiceOptions;
import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions;
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
Expand Down Expand Up @@ -528,6 +529,99 @@ public void processP2TrafficCurrentUser(long frequency, int timeslot, Identifier
}
}

/**
* Process a TDMA data channel grant.
* @param channel for data.
* @param timestamp of the event
*/
public void processP2DataChannel(APCO25Channel channel, long timestamp)
{
long frequency = channel != null ? channel.getDownlinkFrequency() : 0;

if(frequency > 0)
{
mLock.lock();

try
{
P25TrafficChannelEventTracker trackerTS1 = getTrackerRemoveIfStale(channel.getDownlinkFrequency(),
P25P1Message.TIMESLOT_1, timestamp);

if(trackerTS1 != null && trackerTS1.exceedsMaxTDMADataDuration())
{
removeTracker(frequency, P25P1Message.TIMESLOT_1);
trackerTS1 = null;
}

if(trackerTS1 == null)
{
P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(DecodeEventType.DATA_CALL,
timestamp, new DataServiceOptions(0))
.channelDescriptor(channel)
.details("TDMA PHASE 2 DATA CHANNEL ACTIVE")
.identifiers(new IdentifierCollection())
.timeslot(P25P1Message.TIMESLOT_1)
.build();

trackerTS1 = new P25TrafficChannelEventTracker(continuationGrantEvent);
addTracker(trackerTS1, frequency, P25P1Message.TIMESLOT_1);
}

//update the ending timestamp so that the duration value is correctly calculated
trackerTS1.updateDurationTraffic(timestamp);
broadcast(trackerTS1);

//Even though we have a tracked 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.
if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !mIgnoreDataCalls &&
(getCurrentControlFrequency() != frequency))
{
Channel trafficChannel = mAvailablePhase2TrafficChannelQueue.poll();

if(trafficChannel != null)
{
requestTrafficChannelStart(trafficChannel, channel, new IdentifierCollection(), timestamp);
}
else
{
trackerTS1.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
}
}

P25TrafficChannelEventTracker trackerTS2 = getTrackerRemoveIfStale(channel.getDownlinkFrequency(),
P25P1Message.TIMESLOT_2, timestamp);

if(trackerTS2 != null && trackerTS2.exceedsMaxTDMADataDuration())
{
removeTracker(frequency, P25P1Message.TIMESLOT_2);
trackerTS2 = null;
}

if(trackerTS2 == null)
{
P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(DecodeEventType.DATA_CALL,
timestamp, new DataServiceOptions(0))
.channelDescriptor(channel)
.details("TDMA PHASE 2 DATA CHANNEL ACTIVE")
.identifiers(new IdentifierCollection())
.timeslot(P25P1Message.TIMESLOT_2)
.build();

trackerTS2 = new P25TrafficChannelEventTracker(continuationGrantEvent);
addTracker(trackerTS2, frequency, P25P1Message.TIMESLOT_2);
}

//update the ending timestamp so that the duration value is correctly calculated
trackerTS2.updateDurationTraffic(timestamp);
broadcast(trackerTS1);
}
finally
{
mLock.unlock();
}
}
}

/**
* Starts a tracked event and updates the duration for a tracked event.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.L3HarrisGroupRegroupExplicitEncryptionCommand;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaAcknowledgeResponse;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaDenyResponse;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExplicitTDMADataChannelAnnouncement;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExtendedFunctionCommand;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelGrant;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelUpdate;
Expand Down Expand Up @@ -1518,6 +1519,10 @@ private void processTSBK(P25P1Message message)
break;
case MOTOROLA_OSP_QUEUED_RESPONSE:
processTSBKQueuedResponse(tsbk);
break;
case MOTOROLA_OSP_TDMA_DATA_CHANNEL:
processTSBKActiveTDMADataChannel(tsbk);
break;
default:
// if(!tsbk.getOpcode().name().startsWith("ISP"))
// {
Expand All @@ -1531,6 +1536,18 @@ private void processTSBK(P25P1Message message)
}
}

/**
* TSBK Motorola TDMA data channel is active.
* @param tsbk with channel
*/
private void processTSBKActiveTDMADataChannel(TSBKMessage tsbk)
{
if(tsbk instanceof MotorolaExplicitTDMADataChannelAnnouncement tdma)
{
mTrafficChannelManager.processP2DataChannel(tdma.getChannel(), tsbk.getTimestamp());
}
}

/**
* TSBK Status messaging
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public enum Opcode
MOTOROLA_OSP_BASE_STATION_ID(11, "CCH BASE STAT ID", "CONTROL CHANNEL BASE STATION ID"),
MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN(14, "CCH PLND SHUTDWN", "CONTROL CHANNEL PLANNED SHUTDOWN"),
MOTOROLA_OSP_OPCODE_15(15, "MOTOROLA OPCODE 15", "MOTOROLA OPCODE 15"),
//Opcode 22 - observed on PA-STARNET VHF Phase 1 CC site: 1690423FFFFFFFFF0000D458 & 9690423FFFFFFFFF0000306C
MOTOROLA_OSP_TDMA_DATA_CHANNEL(22, "MOTOROLA TDMA DATA CHANNEL", "MOTOROLA TDMA DATA CHANNEL"),
MOTOROLA_OSP_UNKNOWN(-1, "MOTOROLA OSP UNKNOWN OPCODE", "MOTOROLA OSP UNKNOWN OPCODE"),

//Vendor: L3Harris, Inbound Service Packet (ISP)
Expand Down Expand Up @@ -299,7 +299,7 @@ public enum Opcode
MOTOROLA_OSP_GROUP_REGROUP_DELETE, MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT,
MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE, MOTOROLA_OSP_TRAFFIC_CHANNEL_ID,
MOTOROLA_OSP_DENY_RESPONSE, MOTOROLA_OSP_SYSTEM_LOADING, MOTOROLA_OSP_BASE_STATION_ID,
MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN, MOTOROLA_OSP_UNKNOWN);
MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN, MOTOROLA_OSP_TDMA_DATA_CHANNEL, MOTOROLA_OSP_UNKNOWN);

/**
* Harris opcodes
Expand Down Expand Up @@ -479,6 +479,8 @@ public static Opcode fromValue(int value, Direction direction, Vendor vendor)
return MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN;
case 0x0F:
return MOTOROLA_OSP_OPCODE_15;
case 0x16:
return MOTOROLA_OSP_TDMA_DATA_CHANNEL;
default:
return MOTOROLA_OSP_UNKNOWN;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaBaseStationId;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaDenyResponse;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaEmergencyAlarmActivation;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExplicitTDMADataChannelAnnouncement;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExtendedFunctionCommand;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupAddCommand;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelGrant;
Expand Down Expand Up @@ -441,6 +442,9 @@ public static TSBKMessage create(Direction direction, P25P1DataUnitID dataUnitID
case MOTOROLA_OSP_OPCODE_15:
tsbk = new MotorolaOpcode15(dataUnitID, message, nac, timestamp);
break;
case MOTOROLA_OSP_TDMA_DATA_CHANNEL:
tsbk = new MotorolaExplicitTDMADataChannelAnnouncement(dataUnitID, message, nac, timestamp);
break;
case MOTOROLA_OSP_UNKNOWN:
tsbk = new UnknownMotorolaOSPMessage(dataUnitID, message, nac, timestamp);
break;
Expand Down
Loading

0 comments on commit d54ef13

Please sign in to comment.