Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2089 Motorola P25P2 TDMA data channel support #2090

Merged
merged 1 commit into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading