Skip to content

Commit

Permalink
#2089 Motorola P25P2 TDMA data channel support (#2090)
Browse files Browse the repository at this point in the history
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
  • Loading branch information
DSheirer and Dennis Sheirer authored Nov 12, 2024
1 parent 552d3a6 commit 5c9c3fa
Show file tree
Hide file tree
Showing 19 changed files with 751 additions and 8 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,19 @@ 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 && tdma.hasChannel())
{
mTrafficChannelManager.processP2DataChannel(tdma.getChannel(), tsbk.getTimestamp());
mNetworkConfigurationMonitor.process(tsbk);
}
}

/**
* TSBK Status messaging
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacentSiteStatusBroadcast;
Expand All @@ -38,6 +39,7 @@
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
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.MotorolaExplicitTDMADataChannelAnnouncement;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.AdjacentStatusBroadcast;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.NetworkStatusBroadcast;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.RFSSStatusBroadcast;
Expand Down Expand Up @@ -79,8 +81,9 @@ public class P25P1NetworkConfigurationMonitor
//Current Site Secondary Control Channels
private Map<String,IChannelDescriptor> mSecondaryControlChannels = new TreeMap<>();

//Current Site Data Channel
//Current Site Data Channel(s)
private SNDCPDataChannelAnnouncementExplicit mSNDCPDataChannel;
private Map<APCO25Channel, MotorolaExplicitTDMADataChannelAnnouncement> mTDMADataChannelMap = new HashMap<>();

//Current Site Services
private SystemServiceBroadcast mTSBKSystemServiceBroadcast;
Expand Down Expand Up @@ -178,6 +181,12 @@ public void process(TSBKMessage tsbk)
mMotorolaBaseStationId = (MotorolaBaseStationId)tsbk;
}
break;
case MOTOROLA_OSP_TDMA_DATA_CHANNEL:
if(tsbk instanceof MotorolaExplicitTDMADataChannelAnnouncement tdma && tdma.hasChannel())
{
mTDMADataChannelMap.put(tdma.getChannel(), tdma);
}
break;
}
}

Expand Down Expand Up @@ -442,11 +451,21 @@ else if(mAMBTCRFSSStatusBroadcast != null)

if(mSNDCPDataChannel != null)
{
sb.append(" CURRENT DATA CHANNEL:").append(mSNDCPDataChannel.getChannel());
sb.append(" CURRENT FDMA DATA CHANNEL:").append(mSNDCPDataChannel.getChannel());
sb.append(" DOWNLINK:").append(mSNDCPDataChannel.getChannel().getDownlinkFrequency());
sb.append(" UPLINK:").append(mSNDCPDataChannel.getChannel().getUplinkFrequency()).append("\n");
}

if(!mTDMADataChannelMap.isEmpty())
{
for(Map.Entry<APCO25Channel, MotorolaExplicitTDMADataChannelAnnouncement> entry: mTDMADataChannelMap.entrySet())
{
sb.append(" ACTIVE TDMA DATA CHANNEL:").append(entry.getKey());
sb.append(" DOWNLINK:").append(entry.getKey().getDownlinkFrequency());
sb.append(" UPLINK:").append(entry.getKey().getUplinkFrequency()).append("\n");
}
}

if(mMotorolaBaseStationId != null)
{
sb.append(" STATION ID/LICENSE: ").append(mMotorolaBaseStationId.getCWID()).append("\n");
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 5c9c3fa

Please sign in to comment.