Skip to content

Commit 5c9c3fa

Browse files
DSheirerDennis Sheirer
andauthored
#2089 Motorola P25P2 TDMA data channel support (#2090)
Co-authored-by: Dennis Sheirer <[email protected]>
1 parent 552d3a6 commit 5c9c3fa

19 files changed

+751
-8
lines changed

src/main/java/io/github/dsheirer/gui/viewer/P25P2Viewer.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ public class P25P2Viewer extends VBox
9090
private static final Logger mLog = LoggerFactory.getLogger(P25P2Viewer.class);
9191
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
9292
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p2";
93+
private static final String LAST_WACN_VALUE = "last.wacn.value.p25p2";
94+
private static final String LAST_SYSTEM_VALUE = "last.system.value.p25p2";
95+
private static final String LAST_NAC_VALUE = "last.nac.value.p25p2";
9396
private static final String FILE_FREQUENCY_REGEX = ".*\\d{8}_\\d{6}_(\\d{9}).*";
9497
private Preferences mPreferences = Preferences.userNodeForPackage(P25P2Viewer.class);
9598
private Button mSelectFileButton;
@@ -415,6 +418,12 @@ private IntegerTextField getWACNTextField()
415418
if(mWACNTextField == null)
416419
{
417420
mWACNTextField = new IntegerTextField();
421+
mWACNTextField.textProperty().addListener((ob, ol, ne) -> mPreferences.putInt(LAST_WACN_VALUE, getWACNTextField().get()));
422+
int previous = mPreferences.getInt(LAST_WACN_VALUE, 0);
423+
if(previous > 0)
424+
{
425+
getWACNTextField().set(previous);
426+
}
418427
}
419428

420429
return mWACNTextField;
@@ -425,6 +434,12 @@ private IntegerTextField getSystemTextField()
425434
if(mSystemTextField == null)
426435
{
427436
mSystemTextField = new IntegerTextField();
437+
mSystemTextField.textProperty().addListener((ob, ol, ne) -> mPreferences.putInt(LAST_SYSTEM_VALUE, getSystemTextField().get()));
438+
int previous = mPreferences.getInt(LAST_SYSTEM_VALUE, 0);
439+
if(previous > 0)
440+
{
441+
getSystemTextField().set(previous);
442+
}
428443
}
429444

430445
return mSystemTextField;
@@ -435,6 +450,12 @@ private IntegerTextField getNACTextField()
435450
if(mNACTextField == null)
436451
{
437452
mNACTextField = new IntegerTextField();
453+
mNACTextField.textProperty().addListener((ob, ol, ne) -> mPreferences.putInt(LAST_NAC_VALUE, getNACTextField().get()));
454+
int previous = mPreferences.getInt(LAST_NAC_VALUE, 0);
455+
if(previous > 0)
456+
{
457+
getNACTextField().set(previous);
458+
}
438459
}
439460

440461
return mNACTextField;

src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelEventTracker.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class P25TrafficChannelEventTracker
3636
{
3737
private static final Logger LOGGER = LoggerFactory.getLogger(P25TrafficChannelEventTracker.class);
3838
private static final long STALE_EVENT_THRESHOLD_MS = 2000;
39+
private static final long MAX_TDMA_DATA_CHANNEL_EVENT_DURATION_MS = 15000;
3940
private P25ChannelGrantEvent mEvent;
4041
private boolean mStarted = false;
4142
private boolean mComplete = false;
@@ -74,6 +75,14 @@ public boolean isStale(long timestamp)
7475
return timestamp - getEvent().getTimeStart() > STALE_EVENT_THRESHOLD_MS;
7576
}
7677

78+
/**
79+
* Indicates if the TDMA data channel duration exceeds the threshold (15 seconds)
80+
*/
81+
public boolean exceedsMaxTDMADataDuration()
82+
{
83+
return getEvent().getDuration() > MAX_TDMA_DATA_CHANNEL_EVENT_DURATION_MS;
84+
}
85+
7786
/**
7887
* Adds the identifier to the tracked event if the event's identifier collection does not already have it.
7988
* @param identifier to add

src/main/java/io/github/dsheirer/module/decode/p25/P25TrafficChannelManager.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
5959
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
6060
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacOpcode;
61+
import io.github.dsheirer.module.decode.p25.reference.DataServiceOptions;
6162
import io.github.dsheirer.module.decode.p25.reference.ServiceOptions;
6263
import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions;
6364
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
@@ -528,6 +529,99 @@ public void processP2TrafficCurrentUser(long frequency, int timeslot, Identifier
528529
}
529530
}
530531

532+
/**
533+
* Process a TDMA data channel grant.
534+
* @param channel for data.
535+
* @param timestamp of the event
536+
*/
537+
public void processP2DataChannel(APCO25Channel channel, long timestamp)
538+
{
539+
long frequency = channel != null ? channel.getDownlinkFrequency() : 0;
540+
541+
if(frequency > 0)
542+
{
543+
mLock.lock();
544+
545+
try
546+
{
547+
P25TrafficChannelEventTracker trackerTS1 = getTrackerRemoveIfStale(channel.getDownlinkFrequency(),
548+
P25P1Message.TIMESLOT_1, timestamp);
549+
550+
if(trackerTS1 != null && trackerTS1.exceedsMaxTDMADataDuration())
551+
{
552+
removeTracker(frequency, P25P1Message.TIMESLOT_1);
553+
trackerTS1 = null;
554+
}
555+
556+
if(trackerTS1 == null)
557+
{
558+
P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(DecodeEventType.DATA_CALL,
559+
timestamp, new DataServiceOptions(0))
560+
.channelDescriptor(channel)
561+
.details("TDMA PHASE 2 DATA CHANNEL ACTIVE")
562+
.identifiers(new IdentifierCollection())
563+
.timeslot(P25P1Message.TIMESLOT_1)
564+
.build();
565+
566+
trackerTS1 = new P25TrafficChannelEventTracker(continuationGrantEvent);
567+
addTracker(trackerTS1, frequency, P25P1Message.TIMESLOT_1);
568+
}
569+
570+
//update the ending timestamp so that the duration value is correctly calculated
571+
trackerTS1.updateDurationTraffic(timestamp);
572+
broadcast(trackerTS1);
573+
574+
//Even though we have a tracked event, the initial channel grant may have been rejected. Check to
575+
// see if there is a traffic channel allocated. If not, allocate one and update the event description.
576+
if(!mAllocatedTrafficChannelMap.containsKey(frequency) && !mIgnoreDataCalls &&
577+
(getCurrentControlFrequency() != frequency))
578+
{
579+
Channel trafficChannel = mAvailablePhase2TrafficChannelQueue.poll();
580+
581+
if(trafficChannel != null)
582+
{
583+
requestTrafficChannelStart(trafficChannel, channel, new IdentifierCollection(), timestamp);
584+
}
585+
else
586+
{
587+
trackerTS1.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
588+
}
589+
}
590+
591+
P25TrafficChannelEventTracker trackerTS2 = getTrackerRemoveIfStale(channel.getDownlinkFrequency(),
592+
P25P1Message.TIMESLOT_2, timestamp);
593+
594+
if(trackerTS2 != null && trackerTS2.exceedsMaxTDMADataDuration())
595+
{
596+
removeTracker(frequency, P25P1Message.TIMESLOT_2);
597+
trackerTS2 = null;
598+
}
599+
600+
if(trackerTS2 == null)
601+
{
602+
P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(DecodeEventType.DATA_CALL,
603+
timestamp, new DataServiceOptions(0))
604+
.channelDescriptor(channel)
605+
.details("TDMA PHASE 2 DATA CHANNEL ACTIVE")
606+
.identifiers(new IdentifierCollection())
607+
.timeslot(P25P1Message.TIMESLOT_2)
608+
.build();
609+
610+
trackerTS2 = new P25TrafficChannelEventTracker(continuationGrantEvent);
611+
addTracker(trackerTS2, frequency, P25P1Message.TIMESLOT_2);
612+
}
613+
614+
//update the ending timestamp so that the duration value is correctly calculated
615+
trackerTS2.updateDurationTraffic(timestamp);
616+
broadcast(trackerTS1);
617+
}
618+
finally
619+
{
620+
mLock.unlock();
621+
}
622+
}
623+
}
624+
531625
/**
532626
* Starts a tracked event and updates the duration for a tracked event.
533627
*

src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1DecoderState.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.harris.osp.L3HarrisGroupRegroupExplicitEncryptionCommand;
119119
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaAcknowledgeResponse;
120120
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaDenyResponse;
121+
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExplicitTDMADataChannelAnnouncement;
121122
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExtendedFunctionCommand;
122123
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelGrant;
123124
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelUpdate;
@@ -1518,6 +1519,10 @@ private void processTSBK(P25P1Message message)
15181519
break;
15191520
case MOTOROLA_OSP_QUEUED_RESPONSE:
15201521
processTSBKQueuedResponse(tsbk);
1522+
break;
1523+
case MOTOROLA_OSP_TDMA_DATA_CHANNEL:
1524+
processTSBKActiveTDMADataChannel(tsbk);
1525+
break;
15211526
default:
15221527
// if(!tsbk.getOpcode().name().startsWith("ISP"))
15231528
// {
@@ -1531,6 +1536,19 @@ private void processTSBK(P25P1Message message)
15311536
}
15321537
}
15331538

1539+
/**
1540+
* TSBK Motorola TDMA data channel is active.
1541+
* @param tsbk with channel
1542+
*/
1543+
private void processTSBKActiveTDMADataChannel(TSBKMessage tsbk)
1544+
{
1545+
if(tsbk instanceof MotorolaExplicitTDMADataChannelAnnouncement tdma && tdma.hasChannel())
1546+
{
1547+
mTrafficChannelManager.processP2DataChannel(tdma.getChannel(), tsbk.getTimestamp());
1548+
mNetworkConfigurationMonitor.process(tsbk);
1549+
}
1550+
}
1551+
15341552
/**
15351553
* TSBK Status messaging
15361554
*/

src/main/java/io/github/dsheirer/module/decode/p25/phase1/P25P1NetworkConfigurationMonitor.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import io.github.dsheirer.channel.IChannelDescriptor;
2323
import io.github.dsheirer.identifier.Identifier;
24+
import io.github.dsheirer.module.decode.p25.identifier.channel.APCO25Channel;
2425
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
2526
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
2627
import io.github.dsheirer.module.decode.p25.phase1.message.lc.standard.LCAdjacentSiteStatusBroadcast;
@@ -38,6 +39,7 @@
3839
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCRFSSStatusBroadcast;
3940
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
4041
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaBaseStationId;
42+
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExplicitTDMADataChannelAnnouncement;
4143
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.AdjacentStatusBroadcast;
4244
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.NetworkStatusBroadcast;
4345
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.RFSSStatusBroadcast;
@@ -79,8 +81,9 @@ public class P25P1NetworkConfigurationMonitor
7981
//Current Site Secondary Control Channels
8082
private Map<String,IChannelDescriptor> mSecondaryControlChannels = new TreeMap<>();
8183

82-
//Current Site Data Channel
84+
//Current Site Data Channel(s)
8385
private SNDCPDataChannelAnnouncementExplicit mSNDCPDataChannel;
86+
private Map<APCO25Channel, MotorolaExplicitTDMADataChannelAnnouncement> mTDMADataChannelMap = new HashMap<>();
8487

8588
//Current Site Services
8689
private SystemServiceBroadcast mTSBKSystemServiceBroadcast;
@@ -178,6 +181,12 @@ public void process(TSBKMessage tsbk)
178181
mMotorolaBaseStationId = (MotorolaBaseStationId)tsbk;
179182
}
180183
break;
184+
case MOTOROLA_OSP_TDMA_DATA_CHANNEL:
185+
if(tsbk instanceof MotorolaExplicitTDMADataChannelAnnouncement tdma && tdma.hasChannel())
186+
{
187+
mTDMADataChannelMap.put(tdma.getChannel(), tdma);
188+
}
189+
break;
181190
}
182191
}
183192

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

443452
if(mSNDCPDataChannel != null)
444453
{
445-
sb.append(" CURRENT DATA CHANNEL:").append(mSNDCPDataChannel.getChannel());
454+
sb.append(" CURRENT FDMA DATA CHANNEL:").append(mSNDCPDataChannel.getChannel());
446455
sb.append(" DOWNLINK:").append(mSNDCPDataChannel.getChannel().getDownlinkFrequency());
447456
sb.append(" UPLINK:").append(mSNDCPDataChannel.getChannel().getUplinkFrequency()).append("\n");
448457
}
449458

459+
if(!mTDMADataChannelMap.isEmpty())
460+
{
461+
for(Map.Entry<APCO25Channel, MotorolaExplicitTDMADataChannelAnnouncement> entry: mTDMADataChannelMap.entrySet())
462+
{
463+
sb.append(" ACTIVE TDMA DATA CHANNEL:").append(entry.getKey());
464+
sb.append(" DOWNLINK:").append(entry.getKey().getDownlinkFrequency());
465+
sb.append(" UPLINK:").append(entry.getKey().getUplinkFrequency()).append("\n");
466+
}
467+
}
468+
450469
if(mMotorolaBaseStationId != null)
451470
{
452471
sb.append(" STATION ID/LICENSE: ").append(mMotorolaBaseStationId.getCWID()).append("\n");

src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/Opcode.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public enum Opcode
181181
MOTOROLA_OSP_BASE_STATION_ID(11, "CCH BASE STAT ID", "CONTROL CHANNEL BASE STATION ID"),
182182
MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN(14, "CCH PLND SHUTDWN", "CONTROL CHANNEL PLANNED SHUTDOWN"),
183183
MOTOROLA_OSP_OPCODE_15(15, "MOTOROLA OPCODE 15", "MOTOROLA OPCODE 15"),
184-
//Opcode 22 - observed on PA-STARNET VHF Phase 1 CC site: 1690423FFFFFFFFF0000D458 & 9690423FFFFFFFFF0000306C
184+
MOTOROLA_OSP_TDMA_DATA_CHANNEL(22, "MOTOROLA TDMA DATA CHANNEL", "MOTOROLA TDMA DATA CHANNEL"),
185185
MOTOROLA_OSP_UNKNOWN(-1, "MOTOROLA OSP UNKNOWN OPCODE", "MOTOROLA OSP UNKNOWN OPCODE"),
186186

187187
//Vendor: L3Harris, Inbound Service Packet (ISP)
@@ -299,7 +299,7 @@ public enum Opcode
299299
MOTOROLA_OSP_GROUP_REGROUP_DELETE, MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_GRANT,
300300
MOTOROLA_OSP_GROUP_REGROUP_CHANNEL_UPDATE, MOTOROLA_OSP_TRAFFIC_CHANNEL_ID,
301301
MOTOROLA_OSP_DENY_RESPONSE, MOTOROLA_OSP_SYSTEM_LOADING, MOTOROLA_OSP_BASE_STATION_ID,
302-
MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN, MOTOROLA_OSP_UNKNOWN);
302+
MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN, MOTOROLA_OSP_TDMA_DATA_CHANNEL, MOTOROLA_OSP_UNKNOWN);
303303

304304
/**
305305
* Harris opcodes
@@ -479,6 +479,8 @@ public static Opcode fromValue(int value, Direction direction, Vendor vendor)
479479
return MOTOROLA_OSP_CONTROL_CHANNEL_PLANNED_SHUTDOWN;
480480
case 0x0F:
481481
return MOTOROLA_OSP_OPCODE_15;
482+
case 0x16:
483+
return MOTOROLA_OSP_TDMA_DATA_CHANNEL;
482484
default:
483485
return MOTOROLA_OSP_UNKNOWN;
484486
}

src/main/java/io/github/dsheirer/module/decode/p25/phase1/message/tsbk/TSBKMessageFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaBaseStationId;
3636
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaDenyResponse;
3737
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaEmergencyAlarmActivation;
38+
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExplicitTDMADataChannelAnnouncement;
3839
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaExtendedFunctionCommand;
3940
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupAddCommand;
4041
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.MotorolaGroupRegroupChannelGrant;
@@ -441,6 +442,9 @@ public static TSBKMessage create(Direction direction, P25P1DataUnitID dataUnitID
441442
case MOTOROLA_OSP_OPCODE_15:
442443
tsbk = new MotorolaOpcode15(dataUnitID, message, nac, timestamp);
443444
break;
445+
case MOTOROLA_OSP_TDMA_DATA_CHANNEL:
446+
tsbk = new MotorolaExplicitTDMADataChannelAnnouncement(dataUnitID, message, nac, timestamp);
447+
break;
444448
case MOTOROLA_OSP_UNKNOWN:
445449
tsbk = new UnknownMotorolaOSPMessage(dataUnitID, message, nac, timestamp);
446450
break;

0 commit comments

Comments
 (0)