mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
Compare commits
10 Commits
v0.6.1-bet
...
v0.6.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db83d5121c | ||
|
|
d61d9c07b0 | ||
|
|
5c9c3fa503 | ||
|
|
552d3a6ce9 | ||
|
|
3bf425f94a | ||
|
|
660a5acd2f | ||
|
|
551277f399 | ||
|
|
a12040be5c | ||
|
|
e4be7dc5f4 | ||
|
|
b615c1e16a |
@@ -105,6 +105,7 @@ dependencies {
|
||||
implementation 'commons-io:commons-io:2.11.0'
|
||||
implementation 'eu.hansolo:charts:1.0.5'
|
||||
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
||||
implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.1' //OSX & Windows aarch64 native libs
|
||||
implementation 'javax.usb:usb-api:1.0.2'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||
implementation 'org.apache.commons:commons-compress:1.26.0'
|
||||
@@ -119,7 +120,6 @@ dependencies {
|
||||
implementation 'org.usb4java:libusb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||
implementation 'io.github.dsheirer:libusb4java-darwin-aarch64:1.3.1' //usb4java native lib for OSX M1 cpu
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
}
|
||||
|
||||
|
||||
@@ -28,4 +28,4 @@
|
||||
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
||||
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
||||
|
||||
projectVersion=0.6.1-beta-3
|
||||
projectVersion=0.6.1
|
||||
@@ -94,7 +94,6 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
{
|
||||
if(mInlineActive)
|
||||
{
|
||||
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
|
||||
if (mInlineRemaining == -1)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
@@ -105,16 +104,34 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
byte[] chunk = Arrays.copyOfRange(audio, audioOffset, Math.min(audioOffset + mInlineRemaining, audio.length));
|
||||
mInlineRemaining -= chunk.length;
|
||||
audioOffset += chunk.length;
|
||||
|
||||
if(mVerboseLogging)
|
||||
{
|
||||
mLog.info("Broadcasting audio data - sending [" + chunk.length + "/" + audio.length + "] bytes");
|
||||
}
|
||||
|
||||
mStreamingSession.write(chunk);
|
||||
if(mInlineRemaining == 0)
|
||||
{
|
||||
mInlineRemaining = mInlineInterval;
|
||||
|
||||
byte[] metadata = IcecastMetadata.formatInline(IcecastMetadata.getTitle(identifierCollection, mAliasModel)).getBytes();
|
||||
|
||||
if(mVerboseLogging)
|
||||
{
|
||||
mLog.info("Sending inline metadata: " + new String(metadata));
|
||||
}
|
||||
|
||||
mStreamingSession.write(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(mVerboseLogging)
|
||||
{
|
||||
mLog.info("Broadcasting audio data [" + audio.length + "] bytes");
|
||||
}
|
||||
mStreamingSession.write(audio);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -65,6 +66,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
private LinkedTransferQueue<AudioSegment> mNewAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||
private ScheduledExecutorService mScheduledExecutorService =
|
||||
Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk audio manager"));
|
||||
private AudioSegmentPrioritySorter mAudioSegmentPrioritySorter = new AudioSegmentPrioritySorter();
|
||||
private ReentrantLock mAudioOutputLock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
@@ -95,6 +98,9 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
{
|
||||
mLog.warn("No audio output devices available");
|
||||
}
|
||||
|
||||
mProcessingTask = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
0, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,36 +195,53 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
}
|
||||
else if(audioSegment.isLinked())
|
||||
{
|
||||
for(AudioOutput audioOutput: mAudioOutputs)
|
||||
mAudioOutputLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(audioOutput.isLinkedTo(audioSegment))
|
||||
for(AudioOutput audioOutput: mAudioOutputs)
|
||||
{
|
||||
it.remove();
|
||||
audioOutput.play(audioSegment);
|
||||
if(audioOutput.isLinkedTo(audioSegment))
|
||||
{
|
||||
it.remove();
|
||||
audioOutput.play(audioSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mAudioOutputLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Sort audio segments by playback priority and assign to empty audio outputs
|
||||
if(!mAudioSegments.isEmpty())
|
||||
{
|
||||
//TODO: change this to take audio segment start time into account also
|
||||
mAudioSegments.sort(Comparator.comparingInt(o -> o.monitorPriorityProperty().get()));
|
||||
mAudioSegments.sort(mAudioSegmentPrioritySorter);
|
||||
|
||||
//Assign empty audio outputs first
|
||||
for(AudioOutput audioOutput: mAudioOutputs)
|
||||
mAudioOutputLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(audioOutput.isEmpty())
|
||||
//Assign empty audio outputs first
|
||||
for(AudioOutput audioOutput: mAudioOutputs)
|
||||
{
|
||||
audioOutput.play(mAudioSegments.remove(0));
|
||||
|
||||
if(mAudioSegments.isEmpty())
|
||||
if(audioOutput.isEmpty())
|
||||
{
|
||||
return;
|
||||
audioOutput.play(mAudioSegments.remove(0));
|
||||
|
||||
if(mAudioSegments.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mAudioOutputLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
//Remove any audio segments marked as complete that didn't get assigned to an output
|
||||
@@ -288,37 +311,39 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
{
|
||||
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_STARTED);
|
||||
|
||||
if(mProcessingTask != null)
|
||||
mAudioOutputLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
mProcessingTask.cancel(true);
|
||||
for(AudioOutput audioOutput: mAudioOutputs)
|
||||
{
|
||||
audioOutput.dispose();
|
||||
}
|
||||
|
||||
mAudioOutputs.clear();
|
||||
|
||||
switch(entry.getMixerChannel())
|
||||
{
|
||||
case MONO:
|
||||
AudioOutput mono = new MonoAudioOutput(entry.getMixer(), mUserPreferences);
|
||||
mAudioOutputs.add(mono);
|
||||
break;
|
||||
case STEREO:
|
||||
AudioOutput left = new StereoAudioOutput(entry.getMixer(), MixerChannel.LEFT, mUserPreferences);
|
||||
mAudioOutputs.add(left);
|
||||
|
||||
AudioOutput right = new StereoAudioOutput(entry.getMixer(), MixerChannel.RIGHT, mUserPreferences);
|
||||
mAudioOutputs.add(right);
|
||||
break;
|
||||
default:
|
||||
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mAudioOutputLock.unlock();
|
||||
}
|
||||
|
||||
for(AudioOutput audioOutput: mAudioOutputs)
|
||||
{
|
||||
audioOutput.dispose();
|
||||
}
|
||||
|
||||
mAudioOutputs.clear();
|
||||
|
||||
switch(entry.getMixerChannel())
|
||||
{
|
||||
case MONO:
|
||||
AudioOutput mono = new MonoAudioOutput(entry.getMixer(), mUserPreferences);
|
||||
mAudioOutputs.add(mono);
|
||||
break;
|
||||
case STEREO:
|
||||
AudioOutput left = new StereoAudioOutput(entry.getMixer(), MixerChannel.LEFT, mUserPreferences);
|
||||
mAudioOutputs.add(left);
|
||||
|
||||
AudioOutput right = new StereoAudioOutput(entry.getMixer(), MixerChannel.RIGHT, mUserPreferences);
|
||||
mAudioOutputs.add(right);
|
||||
break;
|
||||
default:
|
||||
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
|
||||
}
|
||||
|
||||
mProcessingTask = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||
0, 100, TimeUnit.MILLISECONDS);
|
||||
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
|
||||
mMixerChannelConfiguration = entry;
|
||||
}
|
||||
@@ -389,4 +414,30 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio segment comparator for sorting audio segments by: 1)Playback priority and 2)Segment start time
|
||||
*/
|
||||
public class AudioSegmentPrioritySorter implements Comparator<AudioSegment>
|
||||
{
|
||||
@Override
|
||||
public int compare(AudioSegment segment1, AudioSegment segment2)
|
||||
{
|
||||
if(segment1 == null || segment2 == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
//If priority is the same, sort by start time
|
||||
if(segment1.monitorPriorityProperty().get() == segment2.monitorPriorityProperty().get())
|
||||
{
|
||||
return Long.compare(segment1.getStartTimestamp(), segment2.getStartTimestamp());
|
||||
}
|
||||
//Otherwise, sort by priority
|
||||
else
|
||||
{
|
||||
return Integer.compare(segment1.monitorPriorityProperty().get(), segment2.monitorPriorityProperty().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -415,6 +418,12 @@ public class P25P2Viewer extends VBox
|
||||
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;
|
||||
@@ -425,6 +434,12 @@ public class P25P2Viewer extends VBox
|
||||
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;
|
||||
@@ -435,6 +450,12 @@ public class P25P2Viewer extends VBox
|
||||
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;
|
||||
|
||||
@@ -526,10 +526,11 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
|
||||
|
||||
if (geoPosition != null) {
|
||||
if (geoPosition != null)
|
||||
{
|
||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||
.protocol(Protocol.LRRP)
|
||||
.location(geoPosition)
|
||||
.build();
|
||||
|
||||
@@ -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;
|
||||
@@ -74,6 +75,14 @@ public class P25TrafficChannelEventTracker
|
||||
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
|
||||
|
||||
@@ -58,6 +58,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.standard.osp.Net
|
||||
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;
|
||||
@@ -528,6 +529,99 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -118,6 +118,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
|
||||
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;
|
||||
@@ -1518,6 +1519,10 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
||||
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"))
|
||||
// {
|
||||
@@ -1531,6 +1536,19 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@ package io.github.dsheirer.module.decode.p25.phase1;
|
||||
|
||||
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;
|
||||
@@ -38,6 +39,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCNe
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -178,6 +181,12 @@ public class P25P1NetworkConfigurationMonitor
|
||||
mMotorolaBaseStationId = (MotorolaBaseStationId)tsbk;
|
||||
}
|
||||
break;
|
||||
case MOTOROLA_OSP_TDMA_DATA_CHANNEL:
|
||||
if(tsbk instanceof MotorolaExplicitTDMADataChannelAnnouncement tdma && tdma.hasChannel())
|
||||
{
|
||||
mTDMADataChannelMap.put(tdma.getChannel(), tdma);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,11 +451,21 @@ public class P25P1NetworkConfigurationMonitor
|
||||
|
||||
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");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer, 2024 Ilya Smirnov
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -38,6 +38,26 @@ import java.util.List;
|
||||
*/
|
||||
public class MotorolaTalkerAliasComplete extends TimeslotMessage implements IMessage
|
||||
{
|
||||
private static final byte[] LOOKUP_TABLE =
|
||||
{
|
||||
-14, 46, 102, -112, 116, -118, 111, 120, -69, 83, 3, 17, 104, -51, 68, 23,
|
||||
40, 95, 30, -124, 117, 121, 110, -101, 44, -66, 98, 45, -15, 124, -72, -125,
|
||||
-39, 78, 109, 2, 97, 61, -88, 6, -71, -8, -100, 55, 58, 35, -63, 80,
|
||||
-19, -97, -81, 59, -67, -126, -70, -96, -33, -62, 71, 34, -16, -18, -95, -2,
|
||||
-94, 16, 91, 72, 87, -93, 5, 96, 123, 13, -7, 108, -77, 86, 76, -68,
|
||||
41, -92, 15, -20, -74, -91, -90, 60, 127, 107, -76, 33, -83, -82, -60, -56,
|
||||
-59, 93, -34, -32, 29, 25, 75, -58, 12, 63, 90, -57, -31, 89, 85, 84,
|
||||
74, 67, 66, -30, -29, -6, 0, -28, -27, 24, 65, 11, 10, -26, -4, -3,
|
||||
-46, -10, -44, 43, 99, 73, -108, 94, -89, 92, 112, 105, -9, 8, -79, 125,
|
||||
56, -49, -52, -40, 81, -113, -43, -109, 106, -13, -17, 126, -5, 100, -12, 53,
|
||||
39, 7, 49, 20, -121, -104, 118, 52, -54, -110, 51, 27, 79, -116, 9, 64,
|
||||
50, 54, 119, 18, -45, -61, 1, -85, 114, -127, -107, -55, -64, -23, 101, 82,
|
||||
36, 48, 28, -37, -120, -24, -105, -99, 88, 38, 4, 57, -84, 42, -98, -86,
|
||||
37, -41, -50, -21, -106, -11, 14, -115, -36, -87, 47, -35, 31, -22, -111, -73,
|
||||
-42, -119, -117, -47, -80, -103, 19, 122, -25, -102, -75, -122, -1, 70, -123, -78,
|
||||
115, -38, -65, -48, 113, -53, 77, -128, 21, 103, 22, 26, 32, -114, 69, 62
|
||||
};
|
||||
|
||||
private static final IntField SUID_WACN = IntField.length20(OCTET_0_BIT_0);
|
||||
private static final IntField SUID_SYSTEM = IntField.length12(OCTET_2_BIT_16 + 4);
|
||||
private static final IntField SUID_ID = IntField.length24(OCTET_4_BIT_32);
|
||||
@@ -113,9 +133,64 @@ public class MotorolaTalkerAliasComplete extends TimeslotMessage implements IMes
|
||||
{
|
||||
if(mAlias == null)
|
||||
{
|
||||
//BinaryMessage encoded = getEncodedAlias();
|
||||
//TODO: implement decoding of the encoded alias.
|
||||
String alias = "*ALIAS " + getRadio().getValue() + "*"; //Temporary value until we implement decoding
|
||||
byte[] encoded = getEncodedAlias().toByteArray();
|
||||
|
||||
// TODO: check CRC in last two bytes to validate alias has no errors
|
||||
//
|
||||
// Data: wwwww sss iiiiii aaaa...aaaa cccc
|
||||
//
|
||||
// - w = WACN
|
||||
// - s = system
|
||||
// - i = id
|
||||
// - a = encoded alias
|
||||
// - c = CRC-16/GSM of the previous bytes
|
||||
|
||||
// Get number of bytes and characters excluding checksum
|
||||
char bytes = (char)(encoded.length - 2);
|
||||
char chars = (char)(bytes / 2);
|
||||
|
||||
// Create array for decoded computed bytes (big-endian)
|
||||
byte[] decoded = new byte[encoded.length];
|
||||
|
||||
// Set up the long-running accumulator
|
||||
char accumulator = bytes;
|
||||
char byteIndex = 0;
|
||||
|
||||
// Calculate each byte sequentially
|
||||
do
|
||||
{
|
||||
// Multiplication step 1
|
||||
char accumMult = (char)(((accumulator + 65536) % 65536) * 293 + 0x72E9);
|
||||
|
||||
// Lookup table step
|
||||
byte lut = LOOKUP_TABLE[encoded[byteIndex] + 128];
|
||||
byte mult1 = (byte)(lut - (byte)(accumMult >> 8));
|
||||
|
||||
// Incrementing step
|
||||
byte mult2 = 1;
|
||||
byte shortstop = (byte)(accumMult | 0x1);
|
||||
byte increment = (byte)(shortstop << 1);
|
||||
|
||||
while(mult2 != -1 && shortstop != 1) {
|
||||
shortstop = (byte)(shortstop + increment);
|
||||
mult2 += 2;
|
||||
}
|
||||
|
||||
// Multiplication step 2
|
||||
decoded[byteIndex] = (byte)(mult1 * mult2);
|
||||
|
||||
// Update the accumulator
|
||||
accumulator += (char)(((encoded[byteIndex] + 256) % 256) + 1);
|
||||
byteIndex += 1;
|
||||
}
|
||||
while(byteIndex < bytes);
|
||||
|
||||
// Copy decoded bytes (as chars) to our alias string
|
||||
String alias = "";
|
||||
for (char i = 0; i < chars; i++) {
|
||||
alias += (char)((decoded[i * 2] << 8) | decoded[i * 2 + 1]);
|
||||
}
|
||||
|
||||
mAlias = P25TalkerAliasIdentifier.create(alias);
|
||||
}
|
||||
|
||||
@@ -192,8 +267,7 @@ public class MotorolaTalkerAliasComplete extends TimeslotMessage implements IMes
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkgroup());
|
||||
mIdentifiers.add(getRadio());
|
||||
//TODO: uncomment once alias decoding is implemented.
|
||||
// mIdentifiers.add(getAlias());
|
||||
mIdentifiers.add(getAlias());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -479,6 +479,8 @@ public enum Opcode
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp.Mot
|
||||
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;
|
||||
@@ -441,6 +442,9 @@ public class TSBKMessageFactory
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.phase1.message.tsbk.motorola.osp;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
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.identifier.channel.APCO25ExplicitChannel;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DataUnitID;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBandReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.VendorOSPMessage;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Motorola TSBK Opcode 22 (0x16). This seems to be a TDMA data channel announcement message. A curious thing is the
|
||||
* frequency band for the channel number is an FDMA band. I only have two examples of this, but in both examples
|
||||
* the frequency band and channel number align with known frequencies for the system and site and in one of those cases
|
||||
* it was an actual TDMA data channel, so this may or may not be correct.
|
||||
*
|
||||
* Example: PA-STARNET VHF control channel - not announcing a dedicated TDMA channel (15-4095).
|
||||
* 1690423FFFFFFFFF0000D458
|
||||
* 9690423FFFFFFFFF0000306C
|
||||
*
|
||||
* Example: Victoria Radio Network (VRN) Metro Mobile Radio - UHF control channel with a dedicated TDMA data channel.
|
||||
* https://www.radioreference.com/db/sid/7197 Site 1
|
||||
* 1690C23F3060FFFF00001199 0x3060 = 3-96 = 420.6125 MHz
|
||||
* 9690C23F3060FFFF0000F5AD
|
||||
* 1690C23F803EFFFF000030AF 0x803E = 8-62 = 467.9000 MHz.
|
||||
* 9690C23F803EFFFF0000D49B
|
||||
*
|
||||
* This messages correlates with Motorola TDMA MAC OPCODE 139 which contained both sequences x3060 and x803E that were
|
||||
* carried in separate Opcode 22 messages in the VRN control channel examples, but transmitted just this single message
|
||||
* repeatedly on the dedicated TDMA data channel. TDMA Data Channel was active on 420.6125 MHz.
|
||||
* 8B900FFF803EFF3060FF3060FF3060
|
||||
* .... .... .... ....
|
||||
*/
|
||||
public class MotorolaExplicitTDMADataChannelAnnouncement extends VendorOSPMessage implements IFrequencyBandReceiver
|
||||
{
|
||||
private static final IntField DOWNLINK_FREQUENCY_BAND = IntField.length4(OCTET_4_BIT_32);
|
||||
private static final IntField DOWNLINK_CHANNEL_NUMBER = IntField.length12(OCTET_4_BIT_32 + 4);
|
||||
private static final IntField UPLINK_FREQUENCY_BAND = IntField.length4(OCTET_6_BIT_48);
|
||||
private static final IntField UPLINK_CHANNEL_NUMBER = IntField.length12(OCTET_6_BIT_48 + 4);
|
||||
private APCO25Channel mChannel;
|
||||
|
||||
/**
|
||||
* Constructs a TSBK from the binary message sequence.
|
||||
*/
|
||||
public MotorolaExplicitTDMADataChannelAnnouncement(P25P1DataUnitID dataUnitId, CorrectedBinaryMessage message, int nac, long timestamp)
|
||||
{
|
||||
super(dataUnitId, message, nac, timestamp);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getMessageStub());
|
||||
if(hasChannel())
|
||||
{
|
||||
sb.append(" ACTIVE CHAN:").append(getChannel());
|
||||
sb.append(" FREQ:").append(getChannel().getDownlinkFrequency());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" NOT ACTIVE");
|
||||
}
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* TDMA data channel.
|
||||
*/
|
||||
public APCO25Channel getChannel()
|
||||
{
|
||||
if(mChannel == null && hasChannel())
|
||||
{
|
||||
if(hasUplink())
|
||||
{
|
||||
mChannel = APCO25ExplicitChannel.create(getInt(DOWNLINK_FREQUENCY_BAND), getInt(DOWNLINK_CHANNEL_NUMBER),
|
||||
getInt(UPLINK_FREQUENCY_BAND), getInt(UPLINK_CHANNEL_NUMBER));
|
||||
}
|
||||
else
|
||||
{
|
||||
mChannel = APCO25Channel.create(getInt(DOWNLINK_FREQUENCY_BAND), getInt(DOWNLINK_CHANNEL_NUMBER));
|
||||
}
|
||||
}
|
||||
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message has a data channel
|
||||
*/
|
||||
public boolean hasChannel()
|
||||
{
|
||||
return getMessage().getInt(DOWNLINK_FREQUENCY_BAND) != 0xF && getMessage().getInt(DOWNLINK_CHANNEL_NUMBER) != 0xFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message has an uplink frequency band and channel number.
|
||||
*/
|
||||
private boolean hasUplink()
|
||||
{
|
||||
return getMessage().getInt(UPLINK_FREQUENCY_BAND) != 0xF && getMessage().getInt(UPLINK_CHANNEL_NUMBER) != 0xFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IChannelDescriptor> getChannels()
|
||||
{
|
||||
if(hasChannel())
|
||||
{
|
||||
return List.of(getChannel());
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,7 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorol
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserExtended;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaQueuedResponse;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.DatchTimeslot;
|
||||
import io.github.dsheirer.module.decode.p25.reference.VoiceServiceOptions;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import java.util.Collections;
|
||||
@@ -240,6 +241,11 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
||||
//If we're tracking the call event, update the duration on it
|
||||
mTrafficChannelManager.processP2TrafficVoice(getCurrentFrequency(), getTimeslot(), message.getTimestamp());
|
||||
}
|
||||
//Motorola TDMA data channel.
|
||||
else if(message instanceof DatchTimeslot)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA, getTimeslot()));
|
||||
}
|
||||
else if(message instanceof EncryptionSynchronizationSequence ess)
|
||||
{
|
||||
//We don't send any state events for this message since it can only occur in conjunction with
|
||||
@@ -523,6 +529,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
||||
case MOTOROLA_89_GROUP_REGROUP_DELETE:
|
||||
processDynamicRegrouping(message, mac);
|
||||
break;
|
||||
case MOTOROLA_8B_TDMA_DATA_CHANNEL:
|
||||
break;
|
||||
case MOTOROLA_91_TALKER_ALIAS_HEADER:
|
||||
//Unknown
|
||||
break;
|
||||
|
||||
@@ -36,6 +36,7 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorol
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTalkerAliasHeader;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractSignalingTimeslot;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.AbstractVoiceTimeslot;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.DatchTimeslot;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.Timeslot;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.timeslot.Voice2Timeslot;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
@@ -98,7 +99,11 @@ public class P25P2MessageProcessor implements Listener<IMessage>
|
||||
|
||||
for(Timeslot timeslot: sff.getTimeslots())
|
||||
{
|
||||
if(timeslot instanceof AbstractSignalingTimeslot)
|
||||
if(timeslot instanceof DatchTimeslot)
|
||||
{
|
||||
mMessageListener.receive(timeslot);
|
||||
}
|
||||
else if(timeslot instanceof AbstractSignalingTimeslot)
|
||||
{
|
||||
AbstractSignalingTimeslot ast = (AbstractSignalingTimeslot)timeslot;
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.EnumSet;
|
||||
*/
|
||||
public enum DataUnitID
|
||||
{
|
||||
|
||||
VOICE_4(0, 0x00,"VOICE-4"),
|
||||
RESERVED_1(1, 0x17, "RESERVED 1"),
|
||||
RESERVED_2(2, 0x2E, "RESERVED 2"),
|
||||
@@ -36,7 +35,7 @@ public enum DataUnitID
|
||||
RESERVED_7(7, 0x72, "RESERVED 7"),
|
||||
RESERVED_8(8, 0x8D, "RESERVED 8"),
|
||||
SCRAMBLED_FACCH(9, 0x9A, "FACCH-S"),
|
||||
RESERVED_A(10, 0xA3, "RESERVED 10"),
|
||||
SCRAMBLED_DATCH(10, 0xA3, "DATCH-S"), //Motorola APX-Next TDMA Data Channel
|
||||
RESERVED_B(11, 0xB4, "RESERVED 11"),
|
||||
UNSCRAMBLED_SACCH(12, 0xC6, "SACCH-U"),
|
||||
UNSCRAMBLED_LCCH(13, 0xD1, "LOCCH-U"),
|
||||
@@ -133,7 +132,7 @@ public enum DataUnitID
|
||||
case 0x9A:
|
||||
return SCRAMBLED_FACCH;
|
||||
case 0xA3:
|
||||
return RESERVED_A;
|
||||
return SCRAMBLED_DATCH;
|
||||
case 0xB4:
|
||||
return RESERVED_B;
|
||||
case 0xC6:
|
||||
|
||||
@@ -143,8 +143,10 @@ import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorol
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserAbbreviated;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaGroupRegroupVoiceChannelUserExtended;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaQueuedResponse;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTDMADataChannel;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTalkerAliasDataBlock;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaTalkerAliasHeader;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola.MotorolaUnknownOpcode135;
|
||||
import io.github.dsheirer.module.decode.p25.reference.Vendor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -587,8 +589,12 @@ public class MacMessageFactory
|
||||
return new MotorolaGroupRegroupVoiceChannelUpdate(message, offset);
|
||||
case MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND:
|
||||
return new MotorolaGroupRegroupExtendedFunctionCommand(message, offset);
|
||||
case MOTOROLA_87_UNKNOWN_OPCODE_135:
|
||||
return new MotorolaUnknownOpcode135(message, offset);
|
||||
case MOTOROLA_89_GROUP_REGROUP_DELETE:
|
||||
return new MotorolaGroupRegroupDeleteCommand(message, offset);
|
||||
case MOTOROLA_8B_TDMA_DATA_CHANNEL:
|
||||
return new MotorolaTDMADataChannel(message, offset);
|
||||
case MOTOROLA_8F_ACTIVE_GROUP_RADIOS_143:
|
||||
return new MotorolaActiveGroupRadiosOpcode143_x8F(message, offset);
|
||||
case MOTOROLA_BF_ACTIVE_GROUP_RADIOS_191:
|
||||
|
||||
@@ -118,7 +118,9 @@ public enum MacOpcode
|
||||
MOTOROLA_82_ACTIVE_GROUP_RADIOS_130(Vendor.MOTOROLA, 130, "MOTOROLA ACTIVE GROUP RADIOS 130", Integer.MIN_VALUE),
|
||||
MOTOROLA_83_GROUP_REGROUP_VOICE_CHANNEL_UPDATE(Vendor.MOTOROLA, 131, "MOTOROLA INDIVIDUAL REGROUP ACTIVE", 7),
|
||||
MOTOROLA_84_GROUP_REGROUP_EXTENDED_FUNCTION_COMMAND(Vendor.MOTOROLA, 133, "MOTOROLA GROUP REGROUP", 11),
|
||||
MOTOROLA_87_UNKNOWN_OPCODE_135(Vendor.MOTOROLA, 135, "MOTOROLA UNKNOWN OPCODE 135", Integer.MIN_VALUE),
|
||||
MOTOROLA_89_GROUP_REGROUP_DELETE(Vendor.MOTOROLA, 137, "MOTOROLA GROUP REGROUP DELETE", 17),
|
||||
MOTOROLA_8B_TDMA_DATA_CHANNEL(Vendor.MOTOROLA, 139, "MOTOROLA TDMA DATA CHANNEL", Integer.MIN_VALUE),
|
||||
MOTOROLA_8F_ACTIVE_GROUP_RADIOS_143(Vendor.MOTOROLA, 143, "MOTOROLA ACTIVE GROUP RADIOS 143", Integer.MIN_VALUE),
|
||||
MOTOROLA_BF_ACTIVE_GROUP_RADIOS_191(Vendor.MOTOROLA, 191, "MOTOROLA ACTIVE GROUP RADIOS 191", Integer.MIN_VALUE),
|
||||
//Opcode 144 uses STANDARD vendor ID for some reason.
|
||||
|
||||
@@ -70,4 +70,12 @@ public abstract class MacStructureVariableLength extends MacStructure
|
||||
{
|
||||
return getLength(getMessage(), getOffset());
|
||||
}
|
||||
|
||||
/**
|
||||
* Message fragment for this variable length message.
|
||||
*/
|
||||
public CorrectedBinaryMessage getSubMessage()
|
||||
{
|
||||
return getMessage().getSubMessage(getOffset(), getOffset() + (getLength() * 8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.bits.IntField;
|
||||
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.IFrequencyBandReceiver;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Motorola TDMA data channel active announcement. Opcode 139.
|
||||
*
|
||||
* This messages correlates with Motorola FDMA TSBK OPCODE 22 which contained the same sequences x3060 and x803E and
|
||||
* was transmitted on the control channel for this TDMA data channel and seems to be frequency band and channel number
|
||||
* references.
|
||||
* 8B900FFF803EFF3060FF3060FF3060
|
||||
*
|
||||
* Example: Victoria Radio Network (VRN) - UHF control channel with a dedicated TDMA data channel.
|
||||
* It toggled through all four examples.
|
||||
* 1690C23F3060FFFF00001199
|
||||
* 9690C23F3060FFFF0000F5AD
|
||||
* 1690C23F803EFFFF000030AF
|
||||
* 9690C23F803EFFFF0000D49B
|
||||
*/
|
||||
public class MotorolaTDMADataChannel extends MacStructureVendor implements IFrequencyBandReceiver
|
||||
{
|
||||
private static final IntField UNKNOWN_1 = IntField.length8(OCTET_4_BIT_24);
|
||||
private static final IntField FREQUENCY_BAND_1 = IntField.length4(OCTET_5_BIT_32);
|
||||
private static final IntField CHANNEL_NUMBER_1 = IntField.length12(OCTET_5_BIT_32 + 4);
|
||||
private static final IntField UNKNOWN_2 = IntField.length8(OCTET_7_BIT_48);
|
||||
private static final IntField FREQUENCY_BAND_2 = IntField.length4(OCTET_8_BIT_56);
|
||||
private static final IntField CHANNEL_NUMBER_2 = IntField.length12(OCTET_8_BIT_56 + 4);
|
||||
private static final IntField UNKNOWN_3 = IntField.length8(OCTET_10_BIT_72);
|
||||
private static final IntField FREQUENCY_BAND_3 = IntField.length4(OCTET_11_BIT_80);
|
||||
private static final IntField CHANNEL_NUMBER_3 = IntField.length12(OCTET_11_BIT_80 + 4);
|
||||
private static final IntField UNKNOWN_4 = IntField.length8(OCTET_13_BIT_96);
|
||||
private static final IntField FREQUENCY_BAND_4 = IntField.length4(OCTET_14_BIT_104);
|
||||
private static final IntField CHANNEL_NUMBER_4 = IntField.length12(OCTET_14_BIT_104 + 4);
|
||||
|
||||
private APCO25Channel mChannel1;
|
||||
private APCO25Channel mChannel2;
|
||||
private APCO25Channel mChannel3;
|
||||
private APCO25Channel mChannel4;
|
||||
|
||||
/**
|
||||
* Constructs the message
|
||||
*
|
||||
* @param message containing the message bits
|
||||
* @param offset into the message for this structure
|
||||
*/
|
||||
public MotorolaTDMADataChannel(CorrectedBinaryMessage message, int offset)
|
||||
{
|
||||
super(message, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textual representation of this message
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getOpcode());
|
||||
|
||||
if(hasChannel1())
|
||||
{
|
||||
sb.append(" ACTIVE CHAN1:").append(getChannel1());
|
||||
|
||||
if(hasChannel2())
|
||||
{
|
||||
sb.append(" CHAN2:").append(getChannel2());
|
||||
|
||||
if(hasChannel3())
|
||||
{
|
||||
sb.append(" CHAN3:").append(getChannel3());
|
||||
|
||||
if(hasChannel1())
|
||||
{
|
||||
sb.append(" CHAN4:").append(getChannel4());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(" NOT ACTIVE");
|
||||
}
|
||||
|
||||
sb.append(" MSG:").append(getSubMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if message has channel info.
|
||||
*/
|
||||
private boolean hasChannel1()
|
||||
{
|
||||
return getInt(CHANNEL_NUMBER_1) != 0xF && getInt(FREQUENCY_BAND_1) != 0xFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional channel information.
|
||||
*/
|
||||
public APCO25Channel getChannel1()
|
||||
{
|
||||
if(mChannel1 == null)
|
||||
{
|
||||
mChannel1 = APCO25Channel.create(getInt(FREQUENCY_BAND_1), getInt(CHANNEL_NUMBER_1));
|
||||
}
|
||||
|
||||
return mChannel1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if message has channel info.
|
||||
*/
|
||||
private boolean hasChannel2()
|
||||
{
|
||||
return getInt(CHANNEL_NUMBER_2) != 0xF && getInt(FREQUENCY_BAND_2) != 0xFFF &&
|
||||
getInt(FREQUENCY_BAND_2) != getInt(FREQUENCY_BAND_1) &&
|
||||
getInt(CHANNEL_NUMBER_2) != getInt(CHANNEL_NUMBER_1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional channel information.
|
||||
*/
|
||||
public APCO25Channel getChannel2()
|
||||
{
|
||||
if(mChannel2 == null)
|
||||
{
|
||||
mChannel2 = APCO25Channel.create(getInt(FREQUENCY_BAND_2), getInt(CHANNEL_NUMBER_2));
|
||||
}
|
||||
|
||||
return mChannel2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if message has channel info.
|
||||
*/
|
||||
private boolean hasChannel3()
|
||||
{
|
||||
return getInt(CHANNEL_NUMBER_3) != 0xF && getInt(FREQUENCY_BAND_3) != 0xFFF &&
|
||||
getInt(FREQUENCY_BAND_3) != getInt(FREQUENCY_BAND_2) &&
|
||||
getInt(CHANNEL_NUMBER_3) != getInt(CHANNEL_NUMBER_2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional channel information.
|
||||
*/
|
||||
public APCO25Channel getChannel3()
|
||||
{
|
||||
if(mChannel3 == null)
|
||||
{
|
||||
mChannel3 = APCO25Channel.create(getInt(FREQUENCY_BAND_3), getInt(CHANNEL_NUMBER_3));
|
||||
}
|
||||
|
||||
return mChannel3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if message has channel info.
|
||||
*/
|
||||
private boolean hasChannel4()
|
||||
{
|
||||
return getInt(CHANNEL_NUMBER_4) != 0xF && getInt(FREQUENCY_BAND_4) != 0xFFF &&
|
||||
getInt(FREQUENCY_BAND_4) != getInt(FREQUENCY_BAND_3) &&
|
||||
getInt(CHANNEL_NUMBER_4) != getInt(CHANNEL_NUMBER_3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional channel information.
|
||||
*/
|
||||
public APCO25Channel getChannel4()
|
||||
{
|
||||
if(mChannel4 == null)
|
||||
{
|
||||
mChannel4 = APCO25Channel.create(getInt(FREQUENCY_BAND_4), getInt(CHANNEL_NUMBER_4));
|
||||
}
|
||||
|
||||
return mChannel4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of active TDMA data channels.
|
||||
*/
|
||||
public List<APCO25Channel> getActiveChannels()
|
||||
{
|
||||
List<APCO25Channel> channels = new ArrayList<>();
|
||||
if(hasChannel1())
|
||||
{
|
||||
channels.add(getChannel1());
|
||||
|
||||
if(hasChannel2())
|
||||
{
|
||||
channels.add(getChannel2());
|
||||
|
||||
if(hasChannel3())
|
||||
{
|
||||
channels.add(getChannel3());
|
||||
|
||||
if(hasChannel4())
|
||||
{
|
||||
channels.add(getChannel4());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IChannelDescriptor> getChannels()
|
||||
{
|
||||
List<IChannelDescriptor> channels = new ArrayList<>();
|
||||
if(hasChannel1())
|
||||
{
|
||||
channels.add(getChannel1());
|
||||
|
||||
if(hasChannel2())
|
||||
{
|
||||
channels.add(getChannel2());
|
||||
|
||||
if(hasChannel3())
|
||||
{
|
||||
channels.add(getChannel3());
|
||||
|
||||
if(hasChannel4())
|
||||
{
|
||||
channels.add(getChannel4());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.structure.MacStructureVendor;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Motorola Unknown Opcode 135
|
||||
*
|
||||
* This message was observed on a TDMA dedicated data channel for the Victoria Radio Network (VRN). It may be an
|
||||
* indicator that this is a data channel.
|
||||
*/
|
||||
public class MotorolaUnknownOpcode135 extends MacStructureVendor
|
||||
{
|
||||
/**
|
||||
* Constructs the message
|
||||
*
|
||||
* @param message containing the message bits
|
||||
* @param offset into the message for this structure
|
||||
*/
|
||||
public MotorolaUnknownOpcode135(CorrectedBinaryMessage message, int offset)
|
||||
{
|
||||
super(message, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textual representation of this message
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getOpcode());
|
||||
sb.append(" MSG:").append(getSubMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.phase2.timeslot;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Motorola TDMA Data Channel (DATCH) timeslot. Is probably used with APX-NEXT and MDT equipment.
|
||||
*
|
||||
* Analysis:
|
||||
* - It seems to use the standard P25 Phase 2 scrambling sequence.
|
||||
* - The overall signalling doesn't appear to be encrypted because the data does not appear randomized. There's a lot
|
||||
* of repeated messaging with just a few bits changing.
|
||||
* - It doesn't seem to be RS encoded data, or the first nibble is not protected in the RS encoding because the overall
|
||||
* message doesn't change with variations in the first nibble, when there's repeating messages.
|
||||
* - First 4 bits follow a pattern in repeating messages across both timeslots where the first 2 bits follows the pattern
|
||||
* and the 3rd and 4th bits are set across sequences of 3 or 4 messages spanning both timeslots.
|
||||
* - Not sure if the timeslots are independent of each other, or if they are strapped.
|
||||
*/
|
||||
public class DatchTimeslot extends Timeslot
|
||||
{
|
||||
/**
|
||||
* Constructs a scrambled DATCH timeslot
|
||||
*
|
||||
* @param message containing 320 scrambled bits for the timeslot
|
||||
* @param scramblingSequence to descramble the message
|
||||
* @param timeslot of the message
|
||||
* @param timestamp of the message
|
||||
*/
|
||||
public DatchTimeslot(CorrectedBinaryMessage message, BinaryMessage scramblingSequence, int timeslot,
|
||||
long timestamp)
|
||||
{
|
||||
super(message, DataUnitID.SCRAMBLED_DATCH, scramblingSequence, timeslot, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("TS").append(getTimeslot());
|
||||
sb.append(" DATCH-SC TDMA DATA TIMESLOT MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,8 @@ public class TimeslotFactory
|
||||
return new FacchTimeslot(message, scramblingSequence, timeslot, timestamp);
|
||||
case SCRAMBLED_SACCH:
|
||||
return new SacchTimeslot(message, scramblingSequence, timeslot, timestamp);
|
||||
case SCRAMBLED_DATCH:
|
||||
return new DatchTimeslot(message, scramblingSequence, timeslot, timestamp);
|
||||
case UNSCRAMBLED_FACCH:
|
||||
return new FacchTimeslot(message, timeslot, timestamp);
|
||||
case UNSCRAMBLED_SACCH:
|
||||
|
||||
@@ -41,6 +41,7 @@ public class UnknownTimeslot extends Timeslot
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("TS").append(getTimeslot());
|
||||
sb.append(" UNKNOWN TIMESLOT - DATA UNIT ID: ").append(getDataUnitID());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user