#6 DMR Decoder (#947)

* #6 DMR decoder feature - work in progress

* #6 DMR Decoder.

* #6 DMR Decoder.  Code review updates.

Co-authored-by: Zhenyu Mao <maozhenyu@foxmail.com>
Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-10-12 04:57:59 -04:00
committed by GitHub
parent b061db1189
commit 7eec82deee
436 changed files with 36258 additions and 2409 deletions

View File

@@ -19,7 +19,6 @@ package io.github.dsheirer.module.decode;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageProvider;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
@@ -36,12 +35,6 @@ public abstract class Decoder extends Module implements IMessageProvider
{
}
@Override
public void dispose()
{
mMessageListener = null;
}
public void start()
{
//no-op

View File

@@ -21,6 +21,7 @@ package io.github.dsheirer.module.decode;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.alias.action.AliasActionManager;
import io.github.dsheirer.audio.AbstractAudioModule;
import io.github.dsheirer.audio.AudioModule;
import io.github.dsheirer.channel.state.AlwaysUnsquelchedDecoderState;
import io.github.dsheirer.channel.state.State;
@@ -38,6 +39,11 @@ import io.github.dsheirer.module.decode.am.AMDecoder;
import io.github.dsheirer.module.decode.am.DecodeConfigAM;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.DMRDecoder;
import io.github.dsheirer.module.decode.dmr.DMRDecoderState;
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.module.decode.dmr.message.voice.DMRAudioModule;
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState;
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
@@ -80,12 +86,13 @@ import io.github.dsheirer.module.decode.passport.PassportDecoderState;
import io.github.dsheirer.module.decode.passport.PassportMessageFilter;
import io.github.dsheirer.module.decode.tait.Tait1200Decoder;
import io.github.dsheirer.module.decode.tait.Tait1200DecoderState;
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
import io.github.dsheirer.module.demodulate.am.AMDemodulatorModule;
import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.tuner.channel.ChannelRotationMonitor;
import io.github.dsheirer.source.tuner.channel.rotation.ChannelRotationMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -107,18 +114,25 @@ public class DecoderFactory
* @return list of configured decoders
*/
public static List<Module> getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel,
UserPreferences userPreferences)
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager)
{
List<Module> modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences);
List<Module> modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences, trafficChannelManager);
modules.addAll(getAuxiliaryDecoders(channel.getAuxDecodeConfiguration()));
return modules;
}
/**
* Constructs a primary decoder as specified in the decode configuration
*
* @param channelMapModel for channel map lookups
* @param channel configuration
* @param aliasModel for alias lookups
* @param userPreferences instance
* @param trafficChannelManager optional traffic channel manager to use
* @return list of modules to use for a processing chain
*/
public static List<Module> getPrimaryModules(ChannelMapModel channelMapModel, Channel channel,
AliasModel aliasModel, UserPreferences userPreferences)
public static List<Module> getPrimaryModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel,
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager)
{
List<Module> modules = new ArrayList<Module>();
@@ -135,6 +149,10 @@ public class DecoderFactory
case AM:
processAM(channel, modules, aliasList, decodeConfig);
break;
case DMR:
processDMR(channel, userPreferences, modules, aliasList, (DecodeConfigDMR)decodeConfig,
trafficChannelManager);
break;
case NBFM:
processNBFM(channel, modules, aliasList, decodeConfig);
break;
@@ -145,7 +163,7 @@ public class DecoderFactory
processLTRNet(channel, modules, aliasList, (DecodeConfigLTRNet) decodeConfig);
break;
case MPT1327:
processMPT1327(channelMapModel, channel, userPreferences, modules, aliasList, channelType, (DecodeConfigMPT1327) decodeConfig);
processMPT1327(channelMapModel, channel, modules, aliasList, channelType, (DecodeConfigMPT1327) decodeConfig);
break;
case PASSPORT:
processPassport(channel, modules, aliasList, decodeConfig);
@@ -207,7 +225,8 @@ public class DecoderFactory
{
List<State> activeStates = new ArrayList<>();
activeStates.add(State.CONTROL);
modules.add(new ChannelRotationMonitor(activeStates, userPreferences));
modules.add(new ChannelRotationMonitor(activeStates,
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay()));
}
}
@@ -221,7 +240,7 @@ public class DecoderFactory
}
}
private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig) {
private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig) {
DecodeConfigMPT1327 mptConfig = decodeConfig;
ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName());
Sync sync = mptConfig.getSync();
@@ -232,8 +251,8 @@ public class DecoderFactory
// Set max segment audio sample length slightly above call timeout to
// not create a new segment if the processing chain finishes a bit after
// actual call timeout.
int maxAudioSegmentLengthMillis = (callTimeoutMilliseconds + 5000);
modules.add(new AudioModule(aliasList, maxAudioSegmentLengthMillis));
long maxAudioSegmentLengthMillis = (callTimeoutMilliseconds + 5000);
modules.add(new AudioModule(aliasList, AbstractAudioModule.DEFAULT_TIMESLOT, maxAudioSegmentLengthMillis));
SourceType sourceType = channel.getSourceConfiguration().getSourceType();
if(sourceType == SourceType.TUNER || sourceType == SourceType.TUNER_MULTIPLE_FREQUENCIES)
@@ -258,7 +277,8 @@ public class DecoderFactory
{
List<State> activeStates = new ArrayList<>();
activeStates.add(State.CONTROL);
modules.add(new ChannelRotationMonitor(activeStates, userPreferences));
modules.add(new ChannelRotationMonitor(activeStates,
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay()));
}
}
@@ -319,6 +339,57 @@ public class DecoderFactory
}
}
/**
* Creates modules for DMR decoder setup.
*
* Note: on some DMR systems (e.g. Capacity+) we convert standard channels to traffic channels (when rest channel
* changes) and so we reuse the traffic channel manager from the converted channel.
*
* @param channel for the DMR decoder
* @param userPreferences for access to JMBE audio library
* @param modules list to add to
* @param aliasList for the audio module
* @param decodeConfig for the DMR configuration
* @param trafficChannelManager optional traffic channel manager to re-use
*/
private static void processDMR(Channel channel, UserPreferences userPreferences, List<Module> modules,
AliasList aliasList, DecodeConfigDMR decodeConfig,
TrafficChannelManager trafficChannelManager)
{
modules.add(new DMRDecoder(decodeConfig));
DMRTrafficChannelManager dmrTrafficChannelManager = null;
if(channel.isStandardChannel())
{
if(trafficChannelManager instanceof DMRTrafficChannelManager)
{
dmrTrafficChannelManager = (DMRTrafficChannelManager)trafficChannelManager;
}
else
{
dmrTrafficChannelManager = new DMRTrafficChannelManager(channel);
}
modules.add(dmrTrafficChannelManager);
}
modules.add(new DMRDecoderState(channel, 1, dmrTrafficChannelManager));
modules.add(new DMRDecoderState(channel, 2, dmrTrafficChannelManager));
modules.add(new DMRAudioModule(userPreferences, aliasList, 1));
modules.add(new DMRAudioModule(userPreferences, aliasList, 2));
//Add a channel rotation monitor when we have multiple control channel frequencies specified
if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency &&
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).hasMultipleFrequencies())
{
List<State> activeStates = new ArrayList<>();
activeStates.add(State.CONTROL);
modules.add(new ChannelRotationMonitor(activeStates,
((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencyRotationDelay()));
}
}
/**
* Constructs a list of auxiliary decoders, as specified in the configuration
*
@@ -419,6 +490,9 @@ public class DecoderFactory
case PASSPORT:
filters.add(new PassportMessageFilter());
break;
case DMR:
//filters.add(new DMR) //todo: not finished
break;
default:
break;
}
@@ -439,10 +513,12 @@ public class DecoderFactory
{
case AM:
return new DecodeConfigAM();
case LTR_NET:
return new DecodeConfigLTRNet();
case DMR:
return new DecodeConfigDMR();
case LTR:
return new DecodeConfigLTRStandard();
case LTR_NET:
return new DecodeConfigLTRNet();
case MPT1327:
return new DecodeConfigMPT1327();
case NBFM:
@@ -472,6 +548,8 @@ public class DecoderFactory
DecodeConfigAM origAM = (DecodeConfigAM)config;
copyAM.setRecordAudio(origAM.getRecordAudio());
return copyAM;
case DMR:
return new DecodeConfigDMR();
case LTR_NET:
DecodeConfigLTRNet originalLTRNet = (DecodeConfigLTRNet)config;
DecodeConfigLTRNet copyLTRNet = new DecodeConfigLTRNet();

View File

@@ -32,6 +32,7 @@ public enum DecoderType
{
//Primary Decoders
AM("AM", "AM", Protocol.UNKNOWN),
DMR("DMR","DMR", Protocol.DMR),
LTR("LTR", "LTR", Protocol.LTR),
LTR_NET("LTR-Net", "LTR-Net", Protocol.LTR_NET),
MPT1327("MPT1327", "MPT1327", Protocol.MPT1327),
@@ -62,8 +63,9 @@ public enum DecoderType
*/
public static EnumSet<DecoderType> PRIMARY_DECODERS =
EnumSet.of(DecoderType.AM,
DecoderType.LTR_NET,
DecoderType.DMR,
DecoderType.LTR,
DecoderType.LTR_NET,
DecoderType.MPT1327,
DecoderType.NBFM,
DecoderType.P25_PHASE1,
@@ -82,17 +84,14 @@ public enum DecoderType
/**
* Decoders that produce a (recordable) bitstream
*/
public static final EnumSet<DecoderType> BITSTREAM_DECODERS =
EnumSet.of(DecoderType.MPT1327,
DecoderType.P25_PHASE1,
DecoderType.P25_PHASE2);
public static final EnumSet<DecoderType> BITSTREAM_DECODERS = EnumSet.of(DecoderType.DMR,
DecoderType.MPT1327, DecoderType.P25_PHASE1, DecoderType.P25_PHASE2);
/**
* Decoders that produce (recordable) MBE audio codec frames
*/
public static final EnumSet<DecoderType> MBE_AUDIO_CODEC_DECODERS =
EnumSet.of(DecoderType.P25_PHASE1,
DecoderType.P25_PHASE2);
EnumSet.of(DecoderType.DMR, DecoderType.P25_PHASE1, DecoderType.P25_PHASE2);
public Protocol getProtocol()
{

View File

@@ -49,15 +49,6 @@ public abstract class PrimaryDecoder extends Decoder implements ISquelchStatePro
return mDecodeConfiguration;
}
@Override
public void dispose()
{
super.dispose();
mSquelchStateListener = null;
mDecodeConfiguration = null;
}
public void broadcast(SquelchStateEvent state)
{
if(mSquelchStateListener != null)

View File

@@ -29,6 +29,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.controller.config.Configuration;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.am.DecodeConfigAM;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet;
import io.github.dsheirer.module.decode.ltrstandard.DecodeConfigLTRStandard;
import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
@@ -48,7 +49,8 @@ import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
@JsonSubTypes.Type(value = DecodeConfigNBFM.class, name = "decodeConfigNBFM"),
@JsonSubTypes.Type(value = DecodeConfigP25Phase1.class, name = "decodeConfigP25Phase1"),
@JsonSubTypes.Type(value = DecodeConfigP25Phase2.class, name = "decodeConfigP25Phase2"),
@JsonSubTypes.Type(value = DecodeConfigPassport.class, name = "decodeConfigPassport")
@JsonSubTypes.Type(value = DecodeConfigPassport.class, name = "decodeConfigPassport"),
@JsonSubTypes.Type(value = DecodeConfigDMR.class, name = "decodeConfigDMR")
})
@JacksonXmlRootElement(localName = "decode_configuration")
public abstract class DecodeConfiguration extends Configuration
@@ -76,4 +78,10 @@ public abstract class DecodeConfiguration extends Configuration
{
return 1;
}
@JsonIgnore
public int[] getTimeslots()
{
return new int[0];
}
}

View File

@@ -0,0 +1,520 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.DibitDelayBuffer;
import io.github.dsheirer.dsp.symbol.QPSKCarrierLock;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processes a stream of Dibit symbols and performs burst detection and timeslot framing. This framer
* also detects abnormal PLL phase locks and issues PLL phase lock corrections.
*
* Timeslot Map:
* 0: Common Association Channel (CACH) and Slow Link Control (SLC)
* 1. Timeslot 1
* 2. Timeslot 2
*/
public class DMRBurstFramer implements Listener<Dibit>
{
private final static Logger mLog = LoggerFactory.getLogger(DMRBurstFramer.class);
private static final int DMR_SYMBOL_RATE = 4800;
private static final double PLL_PHASE_CORRECTION_90_DEGREES = (double)DMR_SYMBOL_RATE / 4.0;
private static final double PLL_PHASE_CORRECTION_180_DEGREES = (double)DMR_SYMBOL_RATE / 2.0;
private static final int BURST_DIBIT_START_TS1 = 0;
private static final int BURST_DIBIT_START_TS2 = 144;
private static final int BURST_DIBIT_LENGTH = 144;
private static final int TWO_TIMESLOT_DIBIT_LENGTH = BURST_DIBIT_LENGTH * 2;
private static final int SYNC_DIBIT_OFFSET_TS1 = 66;
private static final int SYNC_DIBIT_OFFSET_TS2 = 210;
private static final int SYNC_DIBIT_LENGTH = 24;
private static final int MAX_STREAMING_SYNC_DETECT_BIT_ERRORS = 4;
private static final int MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS = 6;
/**
* Threshold for issuing a sync loss message. This is set to trigger once the dibit count exceeds one second of
* dibits (4800 baud/dibits) plus two bursts dibit length. The two burst length padding is so that we don't reset
* the dibit counter below the burst sync check threshold.
*/
private static final int SYNC_LOSS_MESSAGE_THRESHOLD = DMR_SYMBOL_RATE + TWO_TIMESLOT_DIBIT_LENGTH;
/**
* The message buffer is sized to hold two DMR bursts of 144 dibits (288 bits) each.
*/
private DibitDelayBuffer mMessageBuffer = new DibitDelayBuffer(BURST_DIBIT_LENGTH * 2);
/**
* The sync delay buffer is sized to align sync detection with two bursts being fully loaded into the message and
* timeslot 1 region of the message buffer is aligned with the sync delay buffer.
*
* This delay equals the number of dibits in the second half of the burst payload: 54 dibits (108 bits).
*/
private DibitDelayBuffer mSyncDelayBuffer = new DibitDelayBuffer(54);
/**
* Synchronized indicates that either the primary or secondary sync tracker is currently synchronized to
* one of the timeslot bursts. Once either sync tracker obtains a valid sync pattern, this flag is set
* to true. The synchronized state will remain true as long as at least one of the two sync trackers
* maintains sync state. Once both sync trackers lose sync, the framer will fallback into sync search/detection
* mode to inspect each dibit until a sync match can be (re)discovered.
*/
private boolean mSynchronized = false;
/**
* Tracks the number of dibits received to trigger burst or sync loss message processing
*/
private int mDibitCounter = 0;
/**
* The Sync detection is used to gain initial lock to a sync pattern or to regain sync if lost. The sync detector
* is used to align a DMR burst into the timeslot 1 region of the message dibit delay buffer so that it can be
* inspected to ensure the timeslots are aligned.
*/
private DMRSyncDetector mSyncDetectorTimeslot1 = new DMRSyncDetector(MAX_STREAMING_SYNC_DETECT_BIT_ERRORS,
MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS);
private DMRSyncDetector mSyncDetectorTimeslot2 = new DMRSyncDetector(MAX_STREAMING_SYNC_DETECT_BIT_ERRORS,
MAX_EXPLICIT_SYNC_DETECT_BIT_ERRORS);
/**
* Timeslot sync trackers track the synchronization state for each of the two timeslots.
*/
private SyncTracker mSyncTrackerTimeslot1 = new SyncTracker();
private SyncTracker mSyncTrackerTimeslot2 = new SyncTracker();
/**
* Timeslot alignment tracker tracks that the timeslots in the delay buffer are aligned.
*/
private TimeslotAlignmentTracker mTimeslotAlignmentTracker = new TimeslotAlignmentTracker();
private IDMRBurstDetectListener mBurstDetectListener;
private IPhaseLockedLoop mPhaseLockedLoop;
/**
* Constructs an instance
* @param listener to be notified of framed burst detections and/or sync loss bits processed
* @param phaseLockedLoop to receive symbol alignment corrections
*/
public DMRBurstFramer(IDMRBurstDetectListener listener, IPhaseLockedLoop phaseLockedLoop)
{
mBurstDetectListener = listener;
mPhaseLockedLoop = phaseLockedLoop;
}
/**
* Primary dibit symbol input method
* @param dibit to process
*/
@Override
public void receive(Dibit dibit)
{
mDibitCounter++;
//Feed the message buffer first to ensure buffer contains two full bursts when a sync is detected
mMessageBuffer.put(dibit);
//Feed the sync delay buffer and reassign the delayed dibit to feed the sync detector if we're not synchronized
dibit = mSyncDelayBuffer.getAndPut(dibit);
if(mSynchronized)
{
if(mDibitCounter >= TWO_TIMESLOT_DIBIT_LENGTH)
{
if(mDibitCounter > TWO_TIMESLOT_DIBIT_LENGTH)
{
processSyncLossDibits(mDibitCounter - TWO_TIMESLOT_DIBIT_LENGTH, 0);
}
dispatch();
}
}
else
{
mSyncDetectorTimeslot2.add(dibit);
if(mSyncDetectorTimeslot2.hasSync())
{
dispatch();
}
else if(mDibitCounter >= SYNC_LOSS_MESSAGE_THRESHOLD)
{
processSyncLossDibits(DMR_SYMBOL_RATE, 0);
}
}
}
/**
* Dispatches burst 1 and/or burst 2 that is currently in the message buffer
*/
private void dispatch()
{
if(mBurstDetectListener != null)
{
if(mDibitCounter > TWO_TIMESLOT_DIBIT_LENGTH)
{
processSyncLossDibits(mDibitCounter - TWO_TIMESLOT_DIBIT_LENGTH, 0);
}
mSyncDetectorTimeslot1.setCurrentSyncValue(getSyncValue(SYNC_DIBIT_OFFSET_TS1));
mSyncDetectorTimeslot2.setCurrentSyncValue(getSyncValue(SYNC_DIBIT_OFFSET_TS2));
mSyncTrackerTimeslot1.update(mSyncDetectorTimeslot1.getSyncPattern(), mSyncDetectorTimeslot1.getCarrierLock());
mSyncTrackerTimeslot2.update(mSyncDetectorTimeslot2.getSyncPattern(), mSyncDetectorTimeslot2.getCarrierLock());
CorrectedBinaryMessage burst1 = null;
CorrectedBinaryMessage burst2 = null;
CACH cach1 = null;
CACH cach2 = null;
//If we have valid sync but PLL tracker misalignment, correct both timeslots before we extract messages
if((mSyncTrackerTimeslot1.hasSync() && mSyncTrackerTimeslot1.hasCarrierMisAlignment()) ||
(mSyncTrackerTimeslot2.hasSync() && mSyncTrackerTimeslot2.hasCarrierMisAlignment()))
{
// mLog.debug("Repairing Message Bits - PLL Mis-Alignment Detected");
if(mSyncTrackerTimeslot1.hasCarrierMisAlignment())
{
repairPLLMisalignment(BURST_DIBIT_START_TS1, mSyncTrackerTimeslot1.getCarrierLock());
repairPLLMisalignment(BURST_DIBIT_START_TS2, mSyncTrackerTimeslot1.getCarrierLock());
}
else
{
repairPLLMisalignment(BURST_DIBIT_START_TS1, mSyncTrackerTimeslot2.getCarrierLock());
repairPLLMisalignment(BURST_DIBIT_START_TS2, mSyncTrackerTimeslot2.getCarrierLock());
}
}
if(mSyncTrackerTimeslot1.hasSync())
{
burst1 = mMessageBuffer.getMessage(BURST_DIBIT_START_TS1, BURST_DIBIT_LENGTH);
burst1.incrementCorrectedBitCount(mSyncDetectorTimeslot1.getPatternMatchBitErrorCount());
if(mSyncTrackerTimeslot1.getSyncPattern().hasCACH())
{
cach1 = CACH.getCACH(burst1);
}
}
if(mSyncTrackerTimeslot2.hasSync())
{
burst2 = mMessageBuffer.getMessage(BURST_DIBIT_START_TS2, BURST_DIBIT_LENGTH);
burst2.incrementCorrectedBitCount(mSyncDetectorTimeslot2.getPatternMatchBitErrorCount());
if(mSyncTrackerTimeslot2.getSyncPattern().hasCACH())
{
cach2 = CACH.getCACH(burst2);
}
}
//Update the timeslot alignment tracker using the sync patterns and optional CACHes from each timeslot
mTimeslotAlignmentTracker.update(cach1, cach2, mSyncDetectorTimeslot1.getSyncPattern(),
mSyncDetectorTimeslot2.getSyncPattern());
if(mTimeslotAlignmentTracker.hasSufficientData())
{
if(mTimeslotAlignmentTracker.isAligned())
{
if(burst1 != null)
{
mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 1);
mDibitCounter -= BURST_DIBIT_LENGTH;
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 1);
}
if(burst2 != null)
{
mBurstDetectListener.burstDetected(burst2, mSyncTrackerTimeslot2.getSyncPattern(), 2);
mDibitCounter -= BURST_DIBIT_LENGTH;
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 2);
}
mSynchronized = mSyncTrackerTimeslot1.hasSync() || mSyncTrackerTimeslot2.hasSync();
}
else
{
if(burst1 != null)
{
mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 2);
mDibitCounter -= BURST_DIBIT_LENGTH;
//Transfer the sync pattern from tracker 1 to tracker 2 so we don't lose voice framing
mSyncTrackerTimeslot2.setSyncPattern(mSyncTrackerTimeslot1.getSyncPattern());
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 2);
mSyncTrackerTimeslot2.reset();
}
mTimeslotAlignmentTracker.reset();
mSyncTrackerTimeslot1.reset();
mDibitCounter = BURST_DIBIT_LENGTH;
mSynchronized = true;
}
}
else
{
//When we don't have sufficient timeslot tracking data, dump the messages to timeslot 0 so that they
//don't corrupt the decoder states for either timeslot, until we positively regain timeslot tracking
if(burst1 != null)
{
mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 0);
mDibitCounter -= BURST_DIBIT_LENGTH;
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 0);
}
if(burst2 != null)
{
mBurstDetectListener.burstDetected(burst2, mSyncTrackerTimeslot2.getSyncPattern(), 0);
mDibitCounter -= BURST_DIBIT_LENGTH;
}
else
{
processSyncLossDibits(BURST_DIBIT_LENGTH, 0);
}
mSynchronized = mSyncTrackerTimeslot1.hasSync() || mSyncTrackerTimeslot2.hasSync();
}
if(!mSynchronized)
{
mTimeslotAlignmentTracker.reset();
mSyncTrackerTimeslot1.reset();
mSyncTrackerTimeslot2.reset();
}
if(mDibitCounter < 0)
{
mDibitCounter = 0;
}
}
}
/**
* Repairs the captured dibits in the message buffer when a PLL lock misalignment is detected.
* @param offset to start of burst
* @param carrierLock that indicates the mis-alignment
*/
private void repairPLLMisalignment(int offset, QPSKCarrierLock carrierLock)
{
int end = offset + BURST_DIBIT_LENGTH;
for(int x = offset; x < end; x++)
{
Dibit misalignedDibit = mMessageBuffer.get(x);
mMessageBuffer.set(x, carrierLock.correct(misalignedDibit));
}
if(mPhaseLockedLoop != null)
{
// mLog.warn("Repairing PLL Phase Lock Mis-Alignment - Detected: " + carrierLock);
switch(carrierLock)
{
case PLUS_90:
mPhaseLockedLoop.correctInversion(PLL_PHASE_CORRECTION_90_DEGREES);
break;
case MINUS_90:
mPhaseLockedLoop.correctInversion(-PLL_PHASE_CORRECTION_90_DEGREES);
break;
case INVERTED:
mPhaseLockedLoop.correctInversion(PLL_PHASE_CORRECTION_180_DEGREES);
break;
}
}
}
/**
* Resets the message and sync delay buffers and resets the dibit counter to zero.
*/
public void reset()
{
mDibitCounter = 0;
mMessageBuffer.reset();
mSyncDelayBuffer.reset();
mTimeslotAlignmentTracker.reset();
}
/**
* Calculates the value of the sync field currently in the message buffer as specified by the offset value.
*/
private long getSyncValue(int dibitOffset)
{
Dibit[] syncDibits = mMessageBuffer.getBuffer(dibitOffset, SYNC_DIBIT_LENGTH);
long value = 0;
for(Dibit dibit: syncDibits)
{
value = Long.rotateLeft(value, 2);
value += dibit.getValue();
}
return value;
}
/**
* Processes dibits received without a sync condition
* @param dibitCount with sync loss
*/
private void processSyncLossDibits(int dibitCount, int timeslot)
{
mDibitCounter -= dibitCount;
if(mBurstDetectListener != null)
{
mBurstDetectListener.syncLost(dibitCount * 2, timeslot);
}
}
/**
* Tracks the sync state for a timeslot to account for voice framing that does not transmit a sync pattern in each
* frame.
*/
public class SyncTracker
{
private DMRSyncPattern mSyncPattern = DMRSyncPattern.UNKNOWN;
private QPSKCarrierLock mCarrierLock = QPSKCarrierLock.NORMAL;
/**
* Updates the sync pattern for the most recently detected burst to the specified pattern. When the previous
* sync pattern is one of either base or mobile voice frame and the detected pattern is unknown, automatically
* advances the sync pattern to the next voice frame in the super frame sequence to account for voice
* frames B - F that do not have a sync pattern.
*/
public void update(DMRSyncPattern pattern, QPSKCarrierLock carrierLock)
{
if(pattern == DMRSyncPattern.UNKNOWN)
{
switch(mSyncPattern)
{
case BASE_STATION_VOICE: //Voice frame A
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_B;
break;
case BS_VOICE_FRAME_B:
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_C;
break;
case BS_VOICE_FRAME_C:
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_D;
break;
case BS_VOICE_FRAME_D:
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_E;
break;
case BS_VOICE_FRAME_E:
mSyncPattern = DMRSyncPattern.BS_VOICE_FRAME_F;
break;
case MOBILE_STATION_VOICE: //Voice frame A
case DIRECT_MODE_VOICE_TIMESLOT_1:
case DIRECT_MODE_VOICE_TIMESLOT_2:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_B;
break;
case MS_VOICE_FRAME_B:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_C;
break;
case MS_VOICE_FRAME_C:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_D;
break;
case MS_VOICE_FRAME_D:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_E;
break;
case MS_VOICE_FRAME_E:
mSyncPattern = DMRSyncPattern.MS_VOICE_FRAME_F;
break;
default:
mSyncPattern = pattern;
mCarrierLock = carrierLock;
break;
}
}
else
{
mSyncPattern = pattern;
mCarrierLock = carrierLock;
}
}
/**
* Most recently detected sync pattern
*/
public DMRSyncPattern getSyncPattern()
{
return mSyncPattern;
}
/**
* Explicitly sets the pattern for this tracker
*/
public void setSyncPattern(DMRSyncPattern pattern)
{
mSyncPattern = pattern;
}
/**
* Carrier lock as indicated by the sync detector.
*/
public QPSKCarrierLock getCarrierLock()
{
return mCarrierLock;
}
/**
* Indicates if the burst has a QPSK constellation mis-alignment that has to be corrected
*/
public boolean hasCarrierMisAlignment()
{
return getCarrierLock() != QPSKCarrierLock.NORMAL;
}
/**
* Checks the contents of the message buffer to determine if the message has a valid sync pattern, or if the
* message is a continuation voice frame from a voice super frame. The identified sync pattern and number of
* bit errors for that pattern are stored in the sync detector so that they can be accessed for dispatching
* a message.
*/
public boolean hasSync()
{
return getSyncPattern() != DMRSyncPattern.UNKNOWN;
}
/**
* Resets the sync pattern to unknown
*/
public void reset()
{
mSyncPattern = DMRSyncPattern.UNKNOWN;
}
}
}

View File

@@ -0,0 +1,344 @@
package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.complex.ComplexFIRFilter2;
import io.github.dsheirer.dsp.gain.ComplexFeedForwardGainControl;
import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulator;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBuffer;
import io.github.dsheirer.dsp.psk.pll.CostasLoop;
import io.github.dsheirer.dsp.psk.pll.FrequencyCorrectionSyncMonitor;
import io.github.dsheirer.dsp.psk.pll.PLLBandwidth;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.DibitToByteBufferAssembler;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.FeedbackDecoder;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.IReusableByteBufferProvider;
import io.github.dsheirer.sample.buffer.IReusableComplexBufferListener;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.ISourceEventListener;
import io.github.dsheirer.source.ISourceEventProvider;
import io.github.dsheirer.source.SourceEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* DMR decoder module.
*/
public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener, ISourceEventProvider,
IReusableComplexBufferListener, Listener<ReusableComplexBuffer>, IReusableByteBufferProvider
{
private final static Logger mLog = LoggerFactory.getLogger(DMRDecoder.class);
protected static final float SAMPLE_COUNTER_GAIN = 0.4f;
private static final double SYMBOL_RATE = 4800.0;
private double mSampleRate;
private Broadcaster<Dibit> mDibitBroadcaster = new Broadcaster<>();
private DibitToByteBufferAssembler mByteBufferAssembler = new DibitToByteBufferAssembler(300);
private DMRMessageProcessor mMessageProcessor;
private Listener<SourceEvent> mSourceEventListener;
private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl(32);
private Map<Double,float[]> mBasebandFilters = new HashMap<>();
private ComplexFIRFilter2 mBasebandFilter;
protected InterpolatingSampleBuffer mInterpolatingSampleBuffer;
protected DQPSKDecisionDirectedDemodulator mQPSKDemodulator;
protected CostasLoop mCostasLoop;
protected FrequencyCorrectionSyncMonitor mFrequencyCorrectionSyncMonitor;
protected DMRMessageFramer mMessageFramer;
/**
* Constructs an instance
*/
public DMRDecoder(DecodeConfigDMR config)
{
mMessageProcessor = new DMRMessageProcessor(config);
mMessageProcessor.setMessageListener(getMessageListener());
getDibitBroadcaster().addListener(mByteBufferAssembler);
setSampleRate(25000.0);
}
/**
* DMR decoder type.
*/
@Override
public DecoderType getDecoderType()
{
return DecoderType.DMR;
}
/**
* Message processor
*/
protected DMRMessageProcessor getMessageProcessor()
{
return mMessageProcessor;
}
/**
* Sets the sample rate and configures internal decoder components.
* @param sampleRate
*/
public void setSampleRate(double sampleRate)
{
if(sampleRate <= getSymbolRate() * 2)
{
throw new IllegalArgumentException("Sample rate [" + sampleRate + "] must be >9600 (2 * " +
getSymbolRate() + " symbol rate)");
}
mSampleRate = sampleRate;
mBasebandFilter = new ComplexFIRFilter2(getBasebandFilter());
mCostasLoop = new CostasLoop(getSampleRate(), getSymbolRate());
mCostasLoop.setPLLBandwidth(PLLBandwidth.BW_300);
mFrequencyCorrectionSyncMonitor = new FrequencyCorrectionSyncMonitor(mCostasLoop, this);
mInterpolatingSampleBuffer = new InterpolatingSampleBuffer(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
mQPSKDemodulator = new DQPSKDecisionDirectedDemodulator(mCostasLoop, mInterpolatingSampleBuffer);
if(mMessageFramer != null)
{
getDibitBroadcaster().removeListener(mMessageFramer);
}
//The Costas Loop receives symbol-inversion correction requests when detected.
//The PLL gain monitor receives sync detect/loss signals from the message framer
mMessageFramer = new DMRMessageFramer(mCostasLoop);
mMessageFramer.setSyncDetectListener(mFrequencyCorrectionSyncMonitor);
mMessageFramer.setListener(getMessageProcessor());
mQPSKDemodulator.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
/**
* Primary method for processing incoming complex sample buffers
* @param reusableComplexBuffer containing channelized complex samples
*/
@Override
public void receive(ReusableComplexBuffer reusableComplexBuffer)
{
//User accounting of the incoming buffer is handled by the filter
ReusableComplexBuffer basebandFiltered = filter(reusableComplexBuffer);
//User accounting of the incoming buffer is handled by the gain filter
ReusableComplexBuffer gainApplied = mAGC.filter(basebandFiltered);
mMessageFramer.setCurrentTime(reusableComplexBuffer.getTimestamp());
//User accounting of the filtered buffer is handled by the demodulator
mQPSKDemodulator.receive(gainApplied);
}
/**
* Filters the complex buffer and returns a new reusable complex buffer with the filtered contents.
* @param reusableComplexBuffer to filter
* @return filtered complex buffer
*/
protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer)
{
//User accounting of the incoming buffer is handled by the filter
return mBasebandFilter.filter(reusableComplexBuffer);
}
/**
* Constructs a baseband filter for this decoder using the current sample rate
*/
private float[] getBasebandFilter()
{
//Attempt to reuse a cached (ie already-designed) filter if available
float[] filter = mBasebandFilters.get(getSampleRate());
if(filter == null)
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate((int)getSampleRate())
.passBandCutoff(5100)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandAmplitude(0.0)
.stopBandStart(6500)
.stopBandRipple(0.01)
.build();
try
{
filter = FilterFactory.getTaps(specification);//
}
catch(Exception fde) //FilterDesignException
{
mLog.error("Couldn't design low pass baseband filter for sample rate: " + getSampleRate());
}
if(filter != null)
{
mBasebandFilters.put(getSampleRate(), filter);
}
else
{
throw new IllegalStateException("Couldn't design a DMR baseband filter for sample rate: " + getSampleRate());
}
}
return filter;
}
/**
* Process source events
*/
protected void process(SourceEvent sourceEvent)
{
switch(sourceEvent.getEvent())
{
case NOTIFICATION_SAMPLE_RATE_CHANGE:
mCostasLoop.reset();
setSampleRate(sourceEvent.getValue().doubleValue());
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//Reset the PLL if/when the tuner PPM changes so that we can re-lock
mCostasLoop.reset();
break;
}
}
/**
* Resets this decoder to prepare for processing a new channel
*/
@Override
public void reset()
{
mCostasLoop.reset();
mFrequencyCorrectionSyncMonitor.reset();
}
/**
* Assembler for packaging Dibit stream into reusable byte buffers.
*/
protected Broadcaster<Dibit> getDibitBroadcaster()
{
return mDibitBroadcaster;
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public void setBufferListener(Listener<ReusableByteBuffer> listener)
{
mByteBufferAssembler.setBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public void removeBufferListener(Listener<ReusableByteBuffer> listener)
{
mByteBufferAssembler.removeBufferListener(listener);
}
/**
* Implements the IByteBufferProvider interface - delegates to the byte buffer assembler
*/
@Override
public boolean hasBufferListeners()
{
return mByteBufferAssembler.hasBufferListeners();
}
protected double getSymbolRate()
{
return SYMBOL_RATE;
}
/**
* Current sample rate for this decoder
*/
protected double getSampleRate()
{
return mSampleRate;
}
/**
* Samples per symbol based on current sample rate and symbol rate.
*/
public float getSamplesPerSymbol()
{
return (float)(getSampleRate() / getSymbolRate());
}
/**
* Sets the source event listener to receive source events from this decoder.
*/
@Override
public void setSourceEventListener(Listener<SourceEvent> listener)
{
mSourceEventListener = listener;
}
/**
* Removes a registered source event listener from receiving source events from this decoder
*/
@Override
public void removeSourceEventListener()
{
mSourceEventListener = null;
}
@Override
public Listener<SourceEvent> getSourceEventListener()
{
return new Listener<SourceEvent>()
{
@Override
public void receive(SourceEvent sourceEvent)
{
process(sourceEvent);
}
};
}
/**
* Broadcasts the source event to an optional registered listener. This method should primarily be used to
* issue frequency correction requests to the channel source.
* @param sourceEvent to broadcast
*/
public void broadcast(SourceEvent sourceEvent)
{
if(mSourceEventListener != null)
{
mSourceEventListener.receive(sourceEvent);
}
}
/**
* Listener interface to receive reusable complex buffers
*/
@Override
public Listener<ReusableComplexBuffer> getReusableComplexBufferListener()
{
return DMRDecoder.this;
}
/**
* Starts the decoder
*/
@Override
public void start()
{
//No-op
}
/**
* Stops the decoder
*/
@Override
public void stop()
{
//No-op
}
}

View File

@@ -0,0 +1,123 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 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.dmr;
import io.github.dsheirer.dsp.psk.DQPSKDecisionDirectedDemodulatorInstrumented;
import io.github.dsheirer.dsp.psk.InterpolatingSampleBufferInstrumented;
import io.github.dsheirer.dsp.psk.SymbolDecisionData;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.sample.complex.Complex;
/**
* Instrumented version of DMR decoder
*/
public class DMRDecoderInstrumented extends DMRDecoder
{
private Listener<Double> mPLLPhaseErrorListener;
private Listener<Double> mPLLFrequencyListener;
private Listener<Double> mSamplesPerSymbolListener;
private Listener<Complex> mComplexSymbolListener;
private Listener<ReusableComplexBuffer> mFilteredSymbolListener;
private Listener<SymbolDecisionData> mSymbolDecisionDataListener;
/**
* Instrumented version of the DMR decoder that supports registering listeners to provide access to data as
* it is being processed by the decoder.
*/
public DMRDecoderInstrumented(DecodeConfigDMR config)
{
super(config);
}
/**
* Overrides the filter method so that we can capture the filtered samples for instrumentation
*/
protected ReusableComplexBuffer filter(ReusableComplexBuffer reusableComplexBuffer)
{
ReusableComplexBuffer filtered = super.filter(reusableComplexBuffer);
if(mFilteredSymbolListener != null)
{
filtered.incrementUserCount();
mFilteredSymbolListener.receive(filtered);
}
return filtered;
}
/**
* Overrides this method so we can correctly configure for instrumented operations
*/
public void setSampleRate(double sampleRate)
{
super.setSampleRate(sampleRate);
InterpolatingSampleBufferInstrumented instrumentedBuffer =
new InterpolatingSampleBufferInstrumented(getSamplesPerSymbol(), SAMPLE_COUNTER_GAIN);
mInterpolatingSampleBuffer = instrumentedBuffer;
DQPSKDecisionDirectedDemodulatorInstrumented instrumented = new DQPSKDecisionDirectedDemodulatorInstrumented(mCostasLoop, instrumentedBuffer, getSampleRate());
mQPSKDemodulator = instrumented;
instrumented.setComplexSymbolListener(mComplexSymbolListener);
instrumented.setPLLErrorListener(mPLLPhaseErrorListener);
instrumented.setPLLFrequencyListener(mPLLFrequencyListener);
instrumented.setSymbolDecisionDataListener(mSymbolDecisionDataListener);
instrumented.setSamplesPerSymbolListener(mSamplesPerSymbolListener);
instrumented.setSymbolListener(getDibitBroadcaster());
getDibitBroadcaster().addListener(mMessageFramer);
}
public void setComplexSymbolListener(Listener<Complex> listener)
{
mComplexSymbolListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setComplexSymbolListener(listener);
}
public void setPLLPhaseErrorListener(Listener<Double> listener)
{
mPLLPhaseErrorListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLErrorListener(listener);
}
public void setPLLFrequencyListener(Listener<Double> listener)
{
mPLLFrequencyListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setPLLFrequencyListener(listener);
}
public void setFilteredBufferListener(Listener<ReusableComplexBuffer> listener)
{
mFilteredSymbolListener = listener;
}
public void setSymbolDecisionDataListener(Listener<SymbolDecisionData> listener)
{
mSymbolDecisionDataListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSymbolDecisionDataListener(listener);
}
public void setSamplesPerSymbolListener(Listener<Double> listener)
{
mSamplesPerSymbolListener = listener;
((DQPSKDecisionDirectedDemodulatorInstrumented)mQPSKDemodulator).setSamplesPerSymbolListener(listener);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,429 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.SyncLossMessage;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import io.github.dsheirer.module.decode.dmr.message.DMRMessageFactory;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.record.binary.BinaryReader;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
/**
* DMR Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors
* and issue phase corrections.
*/
public class DMRMessageFramer implements Listener<Dibit>, IDMRBurstDetectListener
{
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageFramer.class);
private static final double DMR_BIT_RATE = 9600.0;
/**
* Provides sync detection and burst framing
*/
private DMRBurstFramer mBurstFramer;
/**
* External listener for messages produced by this framer
*/
private Listener<IMessage> mMessageListener;
/**
* External listener for sync state information
*/
private ISyncDetectListener mSyncDetectListener;
/**
* Tracks current time for use with framed bursts/messages. This value is updated externally from sample buffer
* timestamps and internally as each framed message is received or sync loss is processed.
*/
private long mCurrentTime = System.currentTimeMillis();
/**
* Constructs an instance
*
* @param phaseLockedLoop to receive PLL phase lock error corrections that are identified by the DMRBurstFramer
* when known phase misaligned sync patterns are detected.
*/
public DMRMessageFramer(IPhaseLockedLoop phaseLockedLoop)
{
mBurstFramer = new DMRBurstFramer(this, phaseLockedLoop);
}
/**
* Constructs an instance.
*/
public DMRMessageFramer()
{
this(null);
}
/**
* Registers a sync detect listener to be notified each time a sync pattern and NID are detected.
*/
public void setSyncDetectListener(ISyncDetectListener syncDetectListener)
{
mSyncDetectListener = syncDetectListener;
}
/**
* Current timestamp or timestamp of incoming message buffers that is continuously updated to as
* close as possible to the bits processed for the expected baud rate.
*
* @return current time
*/
private long getTimestamp()
{
return mCurrentTime;
}
/**
* Sets the current time. This should be invoked by an incoming message buffer stream.
*
* @param currentTime
*/
public void setCurrentTime(long currentTime)
{
mCurrentTime = currentTime;
}
/**
* Updates the current timestamp based on the number of bits processed versus the bit rate per second
* in order to keep an accurate running timestamp to use for timestamped message creation.
*
* @param bitsProcessed thus far
*/
private void updateBitsProcessed(int bitsProcessed)
{
if(bitsProcessed > 0)
{
mCurrentTime += (long)((double)bitsProcessed / DMR_BIT_RATE * 1000.0);
}
}
/**
* Registers the listener for messages produced by this message framer
*
* @param messageListener to receive framed and decoded messages
*/
public void setListener(Listener<IMessage> messageListener)
{
mMessageListener = messageListener;
}
/**
* Primary method for streaming decoded symbol dibits for message framing.
*
* @param dibit to process
*/
@Override
public void receive(Dibit dibit)
{
mBurstFramer.receive(dibit);
}
/**
* Primary method for streaming decoded symbol byte arrays.
*
* @param buffer to process into a stream of dibits for processing.
*/
public void receive(ReusableByteBuffer buffer)
{
//Updates current timestamp to the timestamp from the incoming buffer
setCurrentTime(buffer.getTimestamp());
for(byte value : buffer.getBytes())
{
for(int x = 0; x <= 3; x++)
{
receive(Dibit.parse(value, x));
}
}
buffer.decrementUserCount();
}
/**
* DMR burst detection processing. Processes DMR burst/messages from external burst framer/producer.
*
* @param message bits for the burst/message and any bit errors detected in the sync pattern
* @param syncPattern that was detected for the burst
*/
@Override
public void burstDetected(CorrectedBinaryMessage message, DMRSyncPattern syncPattern, int timeslot)
{
//Each burst is 288 bits
updateBitsProcessed(288);
//This allows the frequency error monitor to be notified when we're in sync
if(mSyncDetectListener != null)
{
mSyncDetectListener.syncDetected(0);
}
CACH cach = null;
if(syncPattern.hasCACH())
{
cach = CACH.getCACH(message);
}
if(mMessageListener != null)
{
DMRMessage dmrMessage = DMRMessageFactory.create(syncPattern, message, cach, getTimestamp(), timeslot);
if(dmrMessage != null)
{
mMessageListener.receive(dmrMessage);
}
}
}
/**
* Processes a sync loss to track how many bits were processed and update message listeners.
*
* @param bitsProcessed since the last sync detect
*/
@Override
public void syncLost(int bitsProcessed, int timeslot)
{
updateBitsProcessed(bitsProcessed);
dispatchSyncLoss(bitsProcessed, timeslot);
if(mSyncDetectListener != null)
{
mSyncDetectListener.syncLost(bitsProcessed);
}
}
/**
* Creates a sync loss message and dispatches it to the message listener
*
* @param bitsProcessed without a sync detection
* @param timeslot that lost the sync
*/
private void dispatchSyncLoss(int bitsProcessed, int timeslot)
{
if(bitsProcessed > 0 && mMessageListener != null)
{
mMessageListener.receive(new SyncLossMessage(getTimestamp(), bitsProcessed, Protocol.DMR, timeslot));
}
}
public static class MessageListener implements Listener<IMessage>
{
private boolean mHasDMRData = false;
public int mTS0Count = 0;
public int mTS1Count = 0;
@Override
public void receive(IMessage message)
{
if(message instanceof DMRBurst && ((DMRBurst)message).hasCACH())
{
CACH cach = ((DMRBurst)message).getCACH();
mLog.info(cach.toString() + " TS:" + message.getTimeslot() + " " + message.toString());
}
else
{
mLog.info(" TS:" + message.getTimeslot() + " " + message.toString());
}
// if(message instanceof UDTHeader)
// {
// mLog.info("TS:" + message.getTimeslot() + " " + message.toString());
// }
// if(message instanceof UnknownCSBKMessage && message.isValid())
// {
// mLog.info("TS:" + ((DMRMessage)message).getTimeslot() + " " + message.toString());
// }
}
public boolean hasData()
{
return mHasDMRData;
}
public void reset()
{
mHasDMRData = true;
}
}
public static void main(String[] args)
{
String path = "/media/denny/Lexar/Recordings/DMR/";
//Con+ Traffic + Voice
// String file = path + "20200513_143340_9600BPS_DMR_SaiaNet_Onondaga_Control.bits"; //Enh GPS Revert Window Annce
// String file = path + "20200514_062135_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Neighbor only
// String file = path + "20200514_063507_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 2579
// String file = path + "20200514_064224_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Window Grant 5056035
// String file = path + "20200514_131623_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //GPS Grant: 5074193
// String file = path + "20200514_133947_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits"; //<<<<<<<------ Basic Encryption
// String file = path + "20200514_142249_9600BPS_DMR_SaiaNet_Onondaga_LCN_4.bits";
// String file = path + "20200514_144534_9600BPS_DMR_SaiaNet_Onondaga_LCN_3_Control.bits"; //Con+ Control w/GPS Window Announce
//Con+ Multi-Site with Network Frequency Map - This file is really General Motors Con+ - Tarrant County site
// String file = path + "20200716_235004_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
//Cap-Max Tier III CC
// String file = path + "20200710_053632_9600BPS_DMR_Niles_Radio_Coconino_Control.bits";
//Cap+ Multi-Site 1 - Traffic LCN 2
// String file = path + "20200716_210133_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
// String file = path + "20200716_212309_9600BPS_DMR_Aerowave_Technologies_Dallas_LCN_2.bits";
//Cap+ Multi-Site Enhanced GPS Channel
// String file = path + "20200714_224018_9600BPS_DMR_Farmers_Electric_Cooperative_Hunt_LCN_3.bits"; //This may have PLL mis-align issues
//Hytera Short Data = Proprietary (encrypted)
// String file = path + "20200716_222839_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits"; //Hytera short data packets
// String file = path + "20200716_223551_9600BPS_DMR_SystemUnk_SiteUnk_Unk.bits";
//Motorola GPS Revert channel
// String file = path + "20200730_085433_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits";
//Hytera XPT (pseudo trunk) beaconing and voice
// String file = path + "20200729_161450_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; //PLL lock oscillating
// String file = path + "20200729_162200_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits";
// String file = path + "Hytera XPT/20200816_161126_9600BPS_DMR_JPJ_Communications_(DMR)_Madison_Control.bits"; //analzing this <<<<<<<<<<<<<<<<<<<<<<<<<<
//Hytera Tier III - CSBKO 40
// String file = path + "20200716_234510_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
// String file = path + "20200812_061410_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits";
// String file = path + "20200716_234510_9600BPS_DMR_Azle_Communications_(Tier_3)_Dallas_Control.bits"; //MBC-channel grant
//UDT Header And Blocks
// String file = path + "20200730_090634_9600BPS_DMR_Otsego_County_Road_Commission_Alpine_Center_Road_Commission.bits";
//Cap+ BP Scrambling
// String file = path + "20200829_065610_9600BPS_DMR_Albany_Medical_Center_Albany_LCN_3.bits";
String file = "/home/denny/SDRTrunk/recordings/20200927_054837_9600BPS_DMR_Blair_Communications_(Capacity_Plus)_Dallas_Control_9.bits";
MessageListener listener = new MessageListener();
DecodeConfigDMR config = new DecodeConfigDMR();
boolean multi = false;
if(multi)
{
try
{
DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(path), "*.bits");
stream.forEach(new Consumer<Path>()
{
@Override
public void accept(Path path)
{
mLog.info("Processing: " + path.toString());
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
messageFramer.setListener(messageProcessor);
messageProcessor.setMessageListener(listener);
try(BinaryReader reader = new BinaryReader(path, 200))
{
while(reader.hasNext())
{
ReusableByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
}
}
catch(Exception ioe)
{
ioe.printStackTrace();
}
if(!listener.hasData())
{
// mLog.info("Has Data: " + listener.hasData() + " File:" + path.toString());
// try
// {
// Files.delete(path);
// }
// catch(IOException ioe)
// {
// ioe.printStackTrace();
// }
}
listener.reset();
System.out.println("TS0 VOICE:" + listener.mTS0Count + " TS1 VOICE:" + listener.mTS1Count);
listener.mTS0Count = 0;
listener.mTS1Count = 0;
}
});
}
catch(IOException ioe)
{
ioe.printStackTrace();
}
}
else
{
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
messageFramer.setListener(messageProcessor);
messageProcessor.setMessageListener(listener);
try(BinaryReader reader = new BinaryReader(Path.of(file), 200))
{
while(reader.hasNext())
{
ReusableByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
}
}
catch(Exception ioe)
{
ioe.printStackTrace();
}
System.out.println("TS0 VOICE:" + listener.mTS0Count + " TS1 VOICE:" + listener.mTS1Count);
listener.mTS0Count = 0;
listener.mTS1Count = 0;
}
}
}

View File

@@ -0,0 +1,218 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.dmr;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FLCAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.SLCAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Processes DMR messages and performs re-assembly of link control fragments
*/
public class DMRMessageProcessor implements Listener<IMessage>
{
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
private MBCAssembler mMBCAssembler = new MBCAssembler();
private PacketSequenceAssembler mPacketSequenceAssembler;
private SLCAssembler mSLCAssembler = new SLCAssembler();
private Listener<IMessage> mMessageListener;
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
/**
* Constructs an instance
*/
public DMRMessageProcessor(DecodeConfigDMR config)
{
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
{
mTimeslotFrequencyMap.put(timeslotFrequency.getNumber(), timeslotFrequency);
}
mPacketSequenceAssembler = new PacketSequenceAssembler();
}
/**
* Primary message processing
*/
@Override
public void receive(IMessage message)
{
//Enrich messages that carry DMR Logical Slot Number channels with LCN to frequency mappings
if(message instanceof ITimeslotFrequencyReceiver)
{
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
int[] lsns = receiver.getLogicalTimeslotNumbers();
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
for(int lsn: lsns)
{
if(mTimeslotFrequencyMap.containsKey(lsn))
{
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lsn));
}
}
if(!timeslotFrequencies.isEmpty())
{
receiver.apply(timeslotFrequencies);
}
}
//Extract the Full Link Control message fragment from the Voice with embedded signalling message
if(message instanceof VoiceEMBMessage)
{
VoiceEMBMessage voice = (VoiceEMBMessage)message;
if(message.getTimeslot() == 1)
{
FullLCMessage flco = mFLCAssemblerTimeslot1.process(voice.getEMB().getLCSS(),
voice.getFLCFragment(), message.getTimestamp());
receive(flco);
}
else
{
FullLCMessage flco = mFLCAssemblerTimeslot2.process(voice.getEMB().getLCSS(),
voice.getFLCFragment(), message.getTimestamp());
receive(flco);
}
if(voice.hasCACH())
{
CACH cach = voice.getCACH();
ShortLCMessage slco = mSLCAssembler.process(cach.getLCSS(), cach.getPayload(), message.getTimestamp());
receive(slco);
}
}
//Extract the Short Link Control message fragment from the DMR burst message when it has one
else if(message instanceof DMRBurst)
{
DMRBurst dmrBurst = (DMRBurst)message;
if(dmrBurst.hasCACH())
{
CACH cach = dmrBurst.getCACH();
ShortLCMessage slco = mSLCAssembler.process(cach.getLCSS(), cach.getPayload(), message.getTimestamp());
receive(slco);
}
//Multi-Block CSBK Reassembly
if(message instanceof MBCHeader)
{
mMBCAssembler.process((MBCHeader)message);
}
else if(message instanceof MBCContinuationBlock)
{
//Returns either a fully reassembled MultiCSBK or null
receive(mMBCAssembler.process((MBCContinuationBlock)message));
}
else
{
mMBCAssembler.reset(message.getTimeslot());
}
//Packet Sequence Message Assembly ...
if(message instanceof Preamble)
{
mPacketSequenceAssembler.process((Preamble)message);
}
else if(message instanceof PacketSequenceHeader)
{
mPacketSequenceAssembler.process((PacketSequenceHeader)message);
}
else if(message instanceof ProprietaryDataHeader)
{
mPacketSequenceAssembler.process(((ProprietaryDataHeader)message));
}
else if(message instanceof DataBlock)
{
mPacketSequenceAssembler.process((DataBlock)message);
}
}
//Now that the message has been (potentially) enriched, dispatch it to the modules
dispatch(message);
}
/**
* Dispatches the non-null message to the registered listener
*/
private void dispatch(IMessage message)
{
if(mMessageListener != null && message != null)
{
mMessageListener.receive(message);
}
}
/**
* Prepares for disposal
*/
public void dispose()
{
mMessageListener = null;
}
/**
* Registers the listener to receive messages from this processor
*/
public void setMessageListener(Listener<IMessage> listener)
{
mMessageListener = listener;
mPacketSequenceAssembler.setMessageListener(listener);
}
/**
* Removes the listener from receiving messages from this processor
*/
public void removeMessageListener()
{
mMessageListener = null;
mPacketSequenceAssembler.setMessageListener(null);
}
}

View File

@@ -0,0 +1,369 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.dmr;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.identifier.site.SiteIdentifier;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.identifier.DMRNetwork;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAdjacentSiteInformation;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusNeighborReport;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Aloha;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.AdjacentSiteInformation;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusControlChannel;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusTrafficChannel;
import io.github.dsheirer.module.decode.dmr.message.type.Model;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Tracks the network configuration details of a DMR network from the broadcast messages
*/
public class DMRNetworkConfigurationMonitor
{
private final static Logger mLog = LoggerFactory.getLogger(DMRNetworkConfigurationMonitor.class);
private static final String BRAND_HYTERA = "Hytera";
private static final String BRAND_MOTOROLA_CAPACITY_PLUS = "Motorola Capacity+";
private static final String BRAND_MOTOROLA_CONNECT_PLUS = "Motorola Connect+";
private static final String BRAND_STANDARD = "Standard";
private static final String BRAND_TIER_3_TRUNKING = "Tier III Trunking";
private static final String BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING = "Capacity Max Tier III Trunking";
private static final String BRAND_HYTERA_TIER_3_TRUNKING = "Hytera Tier III Trunking";
private List<SiteIdentifier> mNeighborSites = new ArrayList<>();
private Map<Integer,AdjacentSiteInformation> mTier3NeighborSites = new HashMap<>();
private Map<Integer,DMRChannel> mObservedChannelMap = new HashMap<>();
private DMRNetwork mDMRNetwork;
private DMRSite mDMRSite;
private Model mTier3Model;
private String mBrand;
private Integer mColorCode;
private DMRChannel mCurrentChannel;
private Channel mChannel;
/**
* Constructs an instance
* @param channel configuration
*/
public DMRNetworkConfigurationMonitor(Channel channel)
{
mChannel = channel;
}
/**
* Sets the current channel
*/
public void setCurrentChannel(DMRChannel channel)
{
mCurrentChannel = channel;
}
/**
* Process a DMR message
* @param message to process that has already been checked for isValid()
*/
public void process(DMRMessage message)
{
if(message instanceof CSBKMessage)
{
process((CSBKMessage)message);
}
else if(message instanceof LCMessage)
{
process((LCMessage)message);
}
}
/**
* Processes link control messages
*/
public void process(LCMessage linkControl)
{
switch(linkControl.getOpcode())
{
case SHORT_CONNECT_PLUS_CONTROL_CHANNEL:
if((mDMRNetwork == null || mDMRSite == null) && linkControl instanceof ConnectPlusControlChannel)
{
ConnectPlusControlChannel cpcc = (ConnectPlusControlChannel)linkControl;
mDMRNetwork = cpcc.getNetwork();
mDMRSite = cpcc.getSite();
}
if(mBrand == null)
{
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
}
break;
case SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL:
if((mDMRNetwork == null || mDMRSite == null) && linkControl instanceof ConnectPlusTrafficChannel)
{
ConnectPlusTrafficChannel cptc = (ConnectPlusTrafficChannel)linkControl;
mDMRNetwork = cptc.getNetwork();
mDMRSite = cptc.getSite();
}
if(mBrand == null)
{
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
}
break;
}
}
/**
* Processes Control Signalling Blocks (CSBK)
*/
public void process(CSBKMessage csbk)
{
switch(csbk.getOpcode())
{
case STANDARD_ALOHA:
if(csbk instanceof Aloha)
{
if(mDMRNetwork == null || mDMRSite == null)
{
SystemIdentityCode sic = ((Aloha)csbk).getSystemIdentityCode();
if(mDMRNetwork == null)
{
mDMRNetwork = sic.getNetwork();
}
if(mDMRSite == null)
{
mDMRSite = sic.getSite();
}
if(mTier3Model == null)
{
mTier3Model = sic.getModel();
}
}
if(mBrand == null)
{
mBrand = BRAND_TIER_3_TRUNKING;
}
}
break;
case HYTERA_08_ANNOUNCEMENT:
case HYTERA_68_ANNOUNCEMENT:
if(csbk instanceof HyteraAnnouncement)
{
HyteraAnnouncement ha = (HyteraAnnouncement)csbk;
if(mBrand == null)
{
mBrand = BRAND_HYTERA_TIER_3_TRUNKING;
}
if(mDMRNetwork == null)
{
mDMRNetwork = ha.getSystemIdentityCode().getNetwork();
}
if(mDMRSite == null)
{
mDMRSite = ha.getSystemIdentityCode().getSite();
}
if(mTier3Model == null)
{
mTier3Model = ha.getSystemIdentityCode().getModel();
}
if(mBrand == null || mBrand != BRAND_HYTERA_TIER_3_TRUNKING)
{
mBrand = BRAND_HYTERA_TIER_3_TRUNKING;
}
}
if(csbk instanceof HyteraAdjacentSiteInformation)
{
HyteraAdjacentSiteInformation hasi = (HyteraAdjacentSiteInformation)csbk;
int site = hasi.getNeighborSystemIdentityCode().getSite().getValue();
if(!mTier3NeighborSites.containsKey(site))
{
mTier3NeighborSites.put(site, hasi);
}
}
break;
case MOTOROLA_CAPMAX_ALOHA:
if(csbk instanceof CapacityMaxAloha)
{
if(mDMRNetwork == null || mDMRSite == null)
{
SystemIdentityCode sic = ((CapacityMaxAloha)csbk).getSystemIdentityCode();
if(mDMRNetwork == null)
{
mDMRNetwork = sic.getNetwork();
}
if(mDMRSite == null)
{
mDMRSite = sic.getSite();
}
if(mTier3Model == null)
{
mTier3Model = sic.getModel();
}
}
if(mBrand == null || mBrand != BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING)
{
mBrand = BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING;
}
}
break;
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
if(mNeighborSites.isEmpty() && csbk instanceof ConnectPlusNeighborReport)
{
ConnectPlusNeighborReport cpnr = (ConnectPlusNeighborReport)csbk;
mNeighborSites.addAll(cpnr.getNeighbors());
}
if(mBrand == null)
{
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
}
break;
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
if(csbk instanceof ConnectPlusVoiceChannelUser)
{
DMRChannel channel = ((ConnectPlusVoiceChannelUser)csbk).getChannel();
addDmrChannel(channel);
}
if(mBrand == null)
{
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
}
break;
}
if(mColorCode == null)
{
mColorCode = csbk.getSlotType().getColorCode();
}
}
/**
* Adds the DMR channel to the observed channel map
*/
private void addDmrChannel(DMRChannel dmrChannel)
{
mObservedChannelMap.put(dmrChannel.getLogicalSlotNumber(), dmrChannel);
}
public void reset()
{
mColorCode = null;
mDMRNetwork = null;
mDMRSite = null;
mNeighborSites.clear();
mTier3NeighborSites.clear();
mObservedChannelMap.clear();
}
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append("Activity Summary - Decoder: DMR ");
//DMR System Brand Name
sb.append("\n\nBrand:").append((mBrand == null ? BRAND_STANDARD : mBrand));
if(mDMRNetwork != null)
{
sb.append("\nNetwork:").append(mDMRNetwork);
}
if(mDMRSite != null)
{
sb.append("\nSite:").append(mDMRSite);
}
if(mTier3Model != null)
{
sb.append("\nNetwork Model").append(mTier3Model);
}
//Observed DMR Channels
if(!mObservedChannelMap.isEmpty())
{
sb.append("\nObserved Logical Slot Numbers (LSN):");
List<Integer> lsns = new ArrayList<>(mObservedChannelMap.keySet());
Collections.sort(lsns);
for(Integer lsn: lsns)
{
DMRChannel channel = mObservedChannelMap.get(lsn);
if(channel != null)
{
sb.append("\n\t").append(channel);
double frequency = channel.getDownlinkFrequency();
if(frequency != 0)
{
frequency /= 1E6d;
}
sb.append(" ").append(frequency).append(" MHz");
}
}
}
if(!mNeighborSites.isEmpty())
{
sb.append("\nNeighbor Sites\n");
for(SiteIdentifier neighbor: mNeighborSites)
{
sb.append("\tNeighbor: ").append(neighbor).append("\n");
}
for(Map.Entry<Integer,AdjacentSiteInformation> neighbor: mTier3NeighborSites.entrySet())
{
AdjacentSiteInformation site = neighbor.getValue();
sb.append("\tNeighbor: ").append("Network:").append(site.getNeighborSystemIdentityCode().getNetwork());
sb.append(" Site:").append(site.getNeighborSystemIdentityCode().getSite());
sb.append(" ").append(site.getNeighborSystemIdentityCode().getModel());
sb.append(" ").append(site.getNeighborChannel()).append("\n");
}
}
sb.append("\n\n");
return sb.toString();
}
}

View File

@@ -0,0 +1,42 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr;
import io.github.dsheirer.controller.channel.event.PreloadDataContent;
/**
* Preload data container for DMRNetworkConfigurationMonitor.
*
* Note: this is used to transfer the monitor during a Capacity plus REST channel rotation operation. The monitor
* is passed from the channel that is converting from REST to a TRAFFIC channel and passed to the new STANDARD channel
* that represents the REST channel.
*/
public class DMRNetworkConfigurationPreloadData extends PreloadDataContent<DMRNetworkConfigurationMonitor>
{
/**
* Constructs an instance
*
* @param data to preload
*/
public DMRNetworkConfigurationPreloadData(DMRNetworkConfigurationMonitor data)
{
super(data);
}
}

View File

@@ -0,0 +1,178 @@
package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.dsp.symbol.Dibit;
import io.github.dsheirer.dsp.symbol.QPSKCarrierLock;
import org.apache.commons.lang3.Validate;
/**
* Detector for DMR sync patterns that also includes support for detecting PLL phase lock errors. There are
* two primary use cases for this class:
*
* The first is to detect a sync pattern from a stream of dibits that are processed via the add(dibit) method. After
* each dibit is added, the sync pattern can be inspected by either the hasSync() method or the getSyncPattern()
* method. The maxStreamBitErrors construction parameter specifies how many bit errors can be present when matching
* patterns to the dibit stream.
*
* The second use case is to set a candidate sync pattern value using the setCurrentSyncValue() method. You can then
* use the hasSync() and getSyncPatter() methods to determine if the set value matches a valid sync pattern. The
* maxExplicitBitErrors specifies how many bit errors can be present when matching patterns to the explicitly set
* sync value.
*
* Note: sync pattern matching maximum bit errors uses a hard-coded value for PLL misalignment sync detection since
* this threshold should be relatively high to avoid false triggering and once detected, a PLL should be immediately
* commanded to correct the issue.
*/
public class DMRSyncDetector
{
private static long SYNC_MASK = 0xFFFFFFFFFFFFl;
private static final int MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT = 1;
private int mMaxStreamBitErrors;
private int mMaxExplicitBitErrors;
private long mCurrentSyncValue;
private long mErrorPattern;
private int mPatternMatchBitErrorCount;
private DMRSyncPattern mSyncPattern = DMRSyncPattern.UNKNOWN;
private QPSKCarrierLock mCarrierLock = QPSKCarrierLock.NORMAL;
/**
* Constructs an instance
* @param maxStreamBitErrors allowed for matching sync patterns when using the dibit streaming use case.
* @param maxExplicitBitErrors allows for matching sync patterns when using the explicitly set sync value use case.
*/
public DMRSyncDetector(int maxStreamBitErrors, int maxExplicitBitErrors)
{
Validate.inclusiveBetween(0, 24, maxStreamBitErrors,
"Max (allowable) stream bit errors for sync match must be between 0 and 24");
Validate.inclusiveBetween(0, 24, maxExplicitBitErrors,
"Max (allowable) explicit bit errors for sync match must be between 0 and 24");
mMaxStreamBitErrors = maxStreamBitErrors;
mMaxExplicitBitErrors = maxExplicitBitErrors;
}
/**
* Sync pattern for the current sync value.
*/
public DMRSyncPattern getSyncPattern()
{
return mSyncPattern;
}
/**
* Indicates if there is a sync pattern in the currently set sync value that was either explicitly set or was
* aggregated from a stream of dibits. When this method returns true, the sync pattern can be accessed via the
* getSyncPattern() method.
*/
public boolean hasSync()
{
return getSyncPattern() != DMRSyncPattern.UNKNOWN;
}
/**
* Carrier lock for the QPSK PLL to detect when the PLL has locked to the signal at +/- 90 degrees or 180 degrees.
* @return carrier lock value.
*/
public QPSKCarrierLock getCarrierLock()
{
return mCarrierLock;
}
/**
* Indicates if the detected sync pattern has normal QPSK carrier lock.
*/
public boolean hasNormalCarrierLock()
{
return getCarrierLock() == QPSKCarrierLock.NORMAL;
}
/**
* Number of bit errors that were present when matching the current sync value to the current sync pattern.
*/
public int getPatternMatchBitErrorCount()
{
return mPatternMatchBitErrorCount;
}
/**
* Processes the streaming dibit by left shifting the current sync value and adding the dibit onto the end and then
* test for sync pattern match.
* @param dibit to process and test for sync pattern.
*/
public void add(Dibit dibit)
{
mCurrentSyncValue = Long.rotateLeft(mCurrentSyncValue, 2);
mCurrentSyncValue &= SYNC_MASK;
mCurrentSyncValue += dibit.getValue();
checkSync(mMaxStreamBitErrors);
}
/**
* Explicitly sets the argument as the current sync value and tests for sync pattern match.
* @param value to load as the current sync value and test
*/
public void setCurrentSyncValue(long value)
{
mCurrentSyncValue = value;
checkSync(mMaxExplicitBitErrors);
}
/**
* Checks the current sync value against each of the sync patterns to determine if the value matches a pattern.
*/
private void checkSync(int maxBitErrors)
{
for(DMRSyncPattern pattern: DMRSyncPattern.SYNC_PATTERNS)
{
mErrorPattern = mCurrentSyncValue ^ pattern.getPattern();
if(mErrorPattern == 0)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.NORMAL;
mPatternMatchBitErrorCount = 0;
return;
}
mPatternMatchBitErrorCount = Long.bitCount(mErrorPattern);
if(mPatternMatchBitErrorCount <= maxBitErrors)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.NORMAL;
return;
}
//For PLL mis-aligned lock patterns, reduce the pattern bit error match threshold
mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getPlus90Pattern());
if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.PLUS_90;
return;
}
mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getMinus90Pattern());
if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.MINUS_90;
return;
}
mPatternMatchBitErrorCount = Long.bitCount(mCurrentSyncValue ^ pattern.getInvertedPattern());
if(mPatternMatchBitErrorCount <= MAX_PATTERN_BIT_ERROR_PLL_MISALIGNMENT)
{
mSyncPattern = pattern;
mCarrierLock = QPSKCarrierLock.INVERTED;
return;
}
}
mSyncPattern = DMRSyncPattern.UNKNOWN;
mCarrierLock = QPSKCarrierLock.NORMAL;
mPatternMatchBitErrorCount = 0;
}
}

View File

@@ -0,0 +1,154 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2020 Dennis Sheirer, Zhenyu Mao
*
* 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.dmr;
import java.util.EnumSet;
/**
* DMR burst sync patterns enumeration. In addition to the ETSI defined sync patterns, each enumeration entry also
* contains derived sync patterns that match decoded bit streams where a QPSK Phase-Locked Loop has locked to the signal
* misaligned by +/- 90 degrees, or 180 degrees. These derived patterns can be used to detect PLL misalignment for
* PLL correction purposes.
*/
public enum DMRSyncPattern
{
BASE_STATION_DATA(0xDFF57D75DF5Dl, 0xBAAFEBEFBAFBl, 0x455014104504l, 0x200A828A20A2l, "BS DATA"),
BASE_STATION_VOICE(0x755FD7DF75F7l, 0xEFFABEBAEFAEl, 0x100541451051l, 0x8AA028208A08l, "BS VOICE A"),
MOBILE_STATION_DATA(0xD5D7F77FD757l, 0xBFBEAEEABEFEl, 0x404151154101l, 0x2A28088028A8l, "MS DATA"),
MOBILE_STATION_VOICE(0x7F7D5DD57DFDl, 0xEAEBFBBFEBABl, 0x151404401454l, 0x8082A22A8202l, "MS VOICE A"),
DIRECT_MODE_DATA_TIMESLOT_1(0xF7FDD5DDFD55l, 0xAEABBFBBABFFl, 0x515440445400l, 0x08022A2202AAl, "DM DAT0"),
DIRECT_MODE_DATA_TIMESLOT_2(0xD7557F5FF7F5l, 0xBEFFEAFAAEAFl, 0x410015055150l, 0x28AA80A0080Al, "DM DAT1"),
DIRECT_MODE_VOICE_TIMESLOT_1(0x5D577F7757FFl, 0xFBFEEAEEFEAAl, 0x040115110155l, 0xA2A88088A800l, "DM VOX0"),
DIRECT_MODE_VOICE_TIMESLOT_2(0x7DFFD5F55D5Fl, 0xEBAABFAFFBFAl, 0x145540500405l, 0x82002A0AA2A0l, "DM VOX1"),
MOBILE_STATION_REVERSE_CHANNEL(0x77D55F7DFD77l, 0xEEBFFAEBABEEl, 0x114005145411l, 0x882AA0820288l, "MS RVRS"),
RESERVED(0xDD7FF5D757DDl, 0xBBEAAFBEFEBBl, 0x441550410144l, 0x22800A28A822l, "RESERVED"),
//These are used to identify the sync-less sub frames of a voice super frame
BS_VOICE_FRAME_B("BS VOICE B"),
BS_VOICE_FRAME_C("BS VOICE C"),
BS_VOICE_FRAME_D("BS VOICE D"),
BS_VOICE_FRAME_E("BS VOICE E"),
BS_VOICE_FRAME_F("BS VOICE F"),
MS_VOICE_FRAME_B("MS VOICE B"),
MS_VOICE_FRAME_C("MS VOICE C"),
MS_VOICE_FRAME_D("MS VOICE D"),
MS_VOICE_FRAME_E("MS VOICE E"),
MS_VOICE_FRAME_F("MS VOICE F"),
UNKNOWN("UNKNOWN");
private long mPattern;
private long mPlus90Pattern;
private long mMinus90Pattern;
private long mInvertedPattern;
private String mLabel;
/**
* DMR Sync Patterns. See TS 102-361-1, paragraph 9.1.1
*/
DMRSyncPattern(long pattern, long plus90Pattern, long minus90Pattern, long invertedPattern, String label)
{
mPattern = pattern;
mPlus90Pattern = plus90Pattern;
mMinus90Pattern = minus90Pattern;
mInvertedPattern = invertedPattern;
mLabel = label;
}
/**
* Alternate constructor for syncs that don't have defined patterns.
*/
DMRSyncPattern(String label)
{
mLabel = label;
}
//Valid sync patterns (excluding the sync-less voice patterns)
public static final EnumSet<DMRSyncPattern> SYNC_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE,
MOBILE_STATION_DATA, MOBILE_STATION_VOICE, DIRECT_MODE_DATA_TIMESLOT_1, DIRECT_MODE_DATA_TIMESLOT_2,
DIRECT_MODE_VOICE_TIMESLOT_1, DIRECT_MODE_VOICE_TIMESLOT_2, MOBILE_STATION_REVERSE_CHANNEL);
//Sync patterns containing a Common Announcement Channel (CACH)
private static final EnumSet<DMRSyncPattern> CACH_PATTERNS = EnumSet.of(BASE_STATION_DATA, BASE_STATION_VOICE,
BS_VOICE_FRAME_B, BS_VOICE_FRAME_C, BS_VOICE_FRAME_D, BS_VOICE_FRAME_E, BS_VOICE_FRAME_F);
//Direct Mode Sync Patterns
private static final EnumSet<DMRSyncPattern> DIRECT_MODE_PATTERNS = EnumSet.of(DIRECT_MODE_DATA_TIMESLOT_1,
DIRECT_MODE_DATA_TIMESLOT_2, DIRECT_MODE_VOICE_TIMESLOT_1, DIRECT_MODE_VOICE_TIMESLOT_2);
/**
* Pattern that represents the enum entry
*/
public long getPattern()
{
return mPattern;
}
/**
* Pattern that represents the PLL locking to the carrier misaligned by plus 90 degrees.
*/
public long getPlus90Pattern()
{
return mPlus90Pattern;
}
/**
* Pattern that represents the PLL locking to the carrier misaligned by minus 90 degrees.
*/
public long getMinus90Pattern()
{
return mMinus90Pattern;
}
/**
* Pattern that represents the PLL locking to the carrier misaligned by 180 degrees (inverted).
*/
public long getInvertedPattern()
{
return mInvertedPattern;
}
/**
* Indicates if a message using this sync pattern contains a Common Announcement Channel (CACH).
*/
public boolean hasCACH()
{
return CACH_PATTERNS.contains(this);
}
/**
* Indicates if this is a Direct Mode sync pattern
*/
public boolean isDirectMode()
{
return DIRECT_MODE_PATTERNS.contains(this);
}
/**
* String representation of the sync pattern
*/
public String toString()
{
return mLabel;
}
}

View File

@@ -0,0 +1,696 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.dmr;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.Channel.ChannelType;
import io.github.dsheirer.controller.channel.ChannelConversionRequest;
import io.github.dsheirer.controller.channel.ChannelEvent;
import io.github.dsheirer.controller.channel.ChannelEvent.Event;
import io.github.dsheirer.controller.channel.IChannelEventListener;
import io.github.dsheirer.controller.channel.IChannelEventProvider;
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.MessageHistoryPreloadData;
import io.github.dsheirer.message.MessageHistoryRequest;
import io.github.dsheirer.message.MessageHistoryResponse;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.event.DMRChannelGrantEvent;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventHistory;
import io.github.dsheirer.module.decode.event.DecodeEventHistoryPreloadData;
import io.github.dsheirer.module.decode.event.DecodeEventHistoryRequest;
import io.github.dsheirer.module.decode.event.DecodeEventHistoryResponse;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.tuner.channel.rotation.DisableChannelRotationMonitorRequest;
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Monitors channel grant and channel grant update messages to allocate traffic channels to capture
* traffic channel activity.
*
* Creates and reuses a limited set of Channel instances each with a TRAFFIC channel type. Since each of
* the traffic channels will be using the same decoder type and configuration options, we reuse each of the
* channel instances to allow the ChannelProcessingManager to reuse a cached set of processing chains that
* are created as each channel is activated.
*
* Each traffic channel is activated by sending a ChannelEvent via the channel model. The channel processing
* manager receives the activation request. If successful, a processing chain is activated for the traffic
* channel. Otherwise, a channel event is broadcast indicating that the channel could not be activated. On
* teardown of an activated traffic channel, a channel event is broadcast to indicate the traffic channels
* is no longer active.
*
* This manager monitors channel events watching for events related to managed traffic channels.
*/
public class DMRTrafficChannelManager extends TrafficChannelManager implements IDecodeEventProvider, IChannelEventListener,
IChannelEventProvider
{
private final static Logger mLog = LoggerFactory.getLogger(DMRTrafficChannelManager.class);
public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED";
public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED";
private Queue<Channel> mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>();
private List<Channel> mManagedTrafficChannels;
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
private Map<Integer,DMRChannelGrantEvent> mLSNGrantEventMap = new ConcurrentHashMap<>();
private Listener<ChannelEvent> mChannelEventListener;
private Listener<IDecodeEvent> mDecodeEventListener;
private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor();
private Channel mParentChannel;
//Used as temporary storage for message and decode event history during Cap+ REST channel rotation
private DecodeEventHistory mTransientDecodeEventHistory;
private List<IMessage> mTransientMessageHistory;
private boolean mIgnoreDataCalls;
private long mCurrentControlFrequency;
/**
* Monitors call events and allocates traffic decoder channels in response
* to traffic channel allocation call events. Manages a pool of reusable
* traffic channel allocations.
*
* @param parentChannel that owns this traffic channel manager
*/
public DMRTrafficChannelManager(Channel parentChannel)
{
mParentChannel = parentChannel;
if(parentChannel.getDecodeConfiguration() instanceof DecodeConfigDMR)
{
mIgnoreDataCalls = ((DecodeConfigDMR)parentChannel.getDecodeConfiguration()).getIgnoreDataCalls();
}
createTrafficChannels();
}
/**
* Sets the current parent control channel frequency so that channel grants for the current frequency do not
* produce an additional traffic channel allocation.
* @param currentControlFrequency for current control channel.
*/
public void setCurrentControlFrequency(long currentControlFrequency)
{
mCurrentControlFrequency = currentControlFrequency;
}
/**
* Creates up to the maximum number of traffic channels for use in allocating traffic channels.
*
* Note: this method uses lazy initialization and will only create the channels once. Subsequent calls will be ignored.
*/
private void createTrafficChannels()
{
if(mManagedTrafficChannels == null)
{
DecodeConfiguration decodeConfiguration = mParentChannel.getDecodeConfiguration();
List<Channel> trafficChannelList = new ArrayList<>();
if(decodeConfiguration instanceof DecodeConfigDMR)
{
DecodeConfigDMR decodeConfig = (DecodeConfigDMR)decodeConfiguration;
int maxTrafficChannels = decodeConfig.getTrafficChannelPoolSize();
if(maxTrafficChannels > 0)
{
for(int x = 0; x < maxTrafficChannels; x++)
{
Channel trafficChannel = new Channel("TRAFFIC", ChannelType.TRAFFIC);
trafficChannel.setAliasListName(mParentChannel.getAliasListName());
trafficChannel.setSystem(mParentChannel.getSystem());
trafficChannel.setSite(mParentChannel.getSite());
trafficChannel.setDecodeConfiguration(decodeConfig);
trafficChannel.setEventLogConfiguration(mParentChannel.getEventLogConfiguration());
trafficChannel.setRecordConfiguration(mParentChannel.getRecordConfiguration());
trafficChannelList.add(trafficChannel);
}
}
}
mAvailableTrafficChannelQueue.addAll(trafficChannelList);
mManagedTrafficChannels = Collections.unmodifiableList(trafficChannelList);
}
}
/**
* Used with Capacity Plus systems to convert the existing standard channel to a traffic channel and then recreate
* the original standard channel with the frequency specified for the new rest channel.
* @param channel
* @param currentFrequency of the standard channel
* @param restChannel to identify the new channel frequency to start
*/
public void convertToTrafficChannel(Channel channel, long currentFrequency, IChannelDescriptor restChannel,
DMRNetworkConfigurationMonitor networkConfigurationMonitor)
{
//Only do the conversion of the original channel has multiple frequencies defined and the rest channel is
//one of those frequencies
if(restChannel.getDownlinkFrequency() > 0 &&
!mAllocatedTrafficChannelFrequencyMap.containsKey(restChannel.getDownlinkFrequency()) &&
channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
{
SourceConfigTunerMultipleFrequency originalSourceConfig = (SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration();
//Add the rest channel to the list of frequencies in the source configuration
if(!originalSourceConfig.getFrequencies().contains(restChannel.getDownlinkFrequency()))
{
originalSourceConfig.addFrequency(restChannel.getDownlinkFrequency());
//Note: the channel configuration modification is not persisted from here, to avoid unnecessary coupling
//to the channel model. The configuration will simply be adjusted at runtime.
}
//Convert current channel to a traffic channel if one is available
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
if(trafficChannel != null && restChannel.getDownlinkFrequency() > 0)
{
//Disable the channel rotation manager when we have multiple frequencies defined
getInterModuleEventBus().post(new DisableChannelRotationMonitorRequest());
//Set the frequency for the traffic channel configuration
SourceConfigTuner trafficSourceConfig = new SourceConfigTuner();
trafficSourceConfig.setFrequency(currentFrequency);
trafficChannel.setSourceConfiguration(trafficSourceConfig);
//Post a request for message and decode event history to transfer to the new REST channel. This has
// to be posted before the channel conversion request
getInterModuleEventBus().post(new DecodeEventHistoryRequest());
getInterModuleEventBus().post(new MessageHistoryRequest());
//Dispatch a request to convert this processing chain to the traffic channel. This will cause the
//processing chain to convert to a traffic channel and notify all of the modules, which will in-turn
//cause the decoder states (2 timeslots) to dereference this manager so that the existing channel can
//no longer allocate traffic channels.
getInterModuleEventBus().post(new ChannelConversionRequest(channel, trafficChannel));
mAllocatedTrafficChannelFrequencyMap.put(currentFrequency, trafficChannel);
//Set the preferred frequency to use when restarting the original channel
originalSourceConfig.setPreferredFrequency(restChannel.getDownlinkFrequency());
//Dispatch request to persistently start the original channel with the rest channel frequency and reuse
//this traffic channel manager in the new processing chain.
ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, this);
request.setPersistentAttempt(true);
request.setChildDecodeEventHistory(mTransientDecodeEventHistory);
//If we received an event history response, add it to the request as preload data content
if(mTransientDecodeEventHistory != null)
{
DecodeEventHistoryPreloadData eventHistory =
new DecodeEventHistoryPreloadData(mTransientDecodeEventHistory.getItems());
request.addPreloadDataContent(eventHistory);
mTransientDecodeEventHistory = null;
}
//If we received a message history response, add it to the request as preload data content
if(mTransientMessageHistory != null)
{
MessageHistoryPreloadData messageHistory = new MessageHistoryPreloadData(mTransientMessageHistory);
request.addPreloadDataContent(messageHistory);
mTransientMessageHistory = null;
}
//Add the DMR network configuration monitor as preload data
if(networkConfigurationMonitor != null)
{
request.addPreloadDataContent(new DMRNetworkConfigurationPreloadData(networkConfigurationMonitor));
}
getInterModuleEventBus().post(request);
}
}
}
/**
* Processes a decode event history response and temporarily stores the event history.
*
* Note: this is used for Cap+ REST channel rotation.
*
* @param response containing the current decode event history.
*/
@Subscribe
public void process(DecodeEventHistoryResponse response)
{
mTransientDecodeEventHistory = response.getDecodeEventHistory();
}
/**
* Processes a message history response and temporarily stores the history.
*
* Note: this is used for Cap+ REST channel rotation.
*
* @param response containing the current message history.
*/
@Subscribe
public void process(MessageHistoryResponse response)
{
mTransientMessageHistory = response.getMessages();
}
/**
* Broadcasts an initial or update decode event to any registered listener.
*/
public void broadcast(DecodeEvent decodeEvent)
{
if(mDecodeEventListener != null)
{
mDecodeEventListener.receive(decodeEvent);
}
}
public void processEndChannelGrant()
{
//TODO: process the CSBK opcode that indicates traffic channel teardown
}
/**
* Processes channel grants to allocate traffic channels and track overall channel usage. Generates
* decode events for each new channel that is allocated.
*
*/
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
Opcode opcode, long timestamp, boolean encrypted)
{
int lsn = channel.getLogicalSlotNumber();
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
if(event != null && isSameCall(identifierCollection, event.getIdentifierCollection()))
{
Identifier from = getIdentifier(identifierCollection, Role.FROM);
if(from != null)
{
Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM);
if(currentFrom != null && !Objects.equals(from, currentFrom))
{
event.end(timestamp);
DMRChannelGrantEvent continuationGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.channel(channel)
.eventDescription(getEventType(opcode, event.isEncrypted()).toString() + " - Continue")
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(lsn, continuationGrantEvent);
broadcast(continuationGrantEvent);
}
}
//update the ending timestamp so that the duration value is correctly calculated
event.update(timestamp);
broadcast(event);
//Even though we have an 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.
long frequency = channel.getDownlinkFrequency();
if(frequency > 0 && frequency != mCurrentControlFrequency &&
!mAllocatedTrafficChannelFrequencyMap.containsKey(channel.getDownlinkFrequency()) &&
!(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode()))
{
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
if(trafficChannel != null)
{
event.setEventDescription(getEventType(opcode, event.isEncrypted()).toString());
event.setDetails("CHANNEL GRANT " + (encrypted ? " ENCRYPTED" : ""));
event.setChannelDescriptor(channel);
broadcast(event);
SourceConfigTuner sourceConfig = new SourceConfigTuner();
sourceConfig.setFrequency(frequency);
trafficChannel.setSourceConfiguration(sourceConfig);
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel);
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel, identifierCollection));
}
else
{
String description = (event.getEventDescription() != null ? event.getEventDescription() : "") +
" - MAX TRAFFIC CHANNELS EXCEEDED";
event.setEventDescription(description);
}
}
return;
}
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
{
DMRChannelGrantEvent channelGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.encrypted(encrypted)
.channel(channel)
.eventDescription(getEventType(opcode, encrypted).toString() + " - Ignored")
.details("DATA CALL IGNORED")
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(channel.getLogicalSlotNumber(), channelGrantEvent);
broadcast(channelGrantEvent);
return;
}
DMRChannelGrantEvent channelGrantEvent = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
.encrypted(encrypted)
.channel(channel)
.eventDescription(getEventType(opcode, encrypted).toString())
.details("CHANNEL GRANT")
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(channel.getLogicalSlotNumber(), channelGrantEvent);
//Allocate a traffic channel for the downlink frequency if one isn't already allocated
//NOTE: we could also allocate a traffic channel for the uplink frequency here, in the future
long frequency = channel.getDownlinkFrequency();
if(frequency > 0 && frequency != mCurrentControlFrequency &&
!mAllocatedTrafficChannelFrequencyMap.containsKey(frequency))
{
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
if(trafficChannel == null)
{
String details = (channelGrantEvent.getDetails() != null ? channelGrantEvent.getDetails() : "") +
" " + MAX_TRAFFIC_CHANNELS_EXCEEDED;
channelGrantEvent.setDetails(details);
channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored");
}
else
{
SourceConfigTuner sourceConfig = new SourceConfigTuner();
sourceConfig.setFrequency(frequency);
trafficChannel.setSourceConfiguration(sourceConfig);
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel);
broadcast(ChannelEvent.requestEnable(trafficChannel));
}
}
broadcast(channelGrantEvent);
}
/**
* Creates a call event type description for the specified opcode and service options
*/
private DecodeEventType getEventType(Opcode opcode, boolean encrypted)
{
DecodeEventType type = null;
switch(opcode)
{
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
break;
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
type = encrypted ? DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED : DecodeEventType.CALL_UNIT_TO_UNIT;
break;
case STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM:
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
case STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT:
case STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM:
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM:
case MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT:
type = encrypted ? DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL;
break;
}
if(type == null)
{
mLog.debug("Unrecognized opcode for determining decode event type: " + opcode.name());
type = DecodeEventType.CALL;
}
return type;
}
/**
* Channel event listener to receive notifications that a traffic channel has ended processing and we
* can reclaim the traffic channel for reuse.
*
* @return listener for processing channel events.
*/
@Override
public Listener<ChannelEvent> getChannelEventListener()
{
return mTrafficChannelTeardownMonitor;
}
/**
* Broadcasts a channel event to a registered external listener (for action).
*/
private void broadcast(ChannelEvent channelEvent)
{
if(mChannelEventListener != null)
{
mChannelEventListener.receive(channelEvent);
}
}
/**
* Sets the external channel event listener to receive channel events from this traffic channel manager
*/
@Override
public void setChannelEventListener(Listener<ChannelEvent> listener)
{
mChannelEventListener = listener;
}
/**
* Removes the channel event listener
*/
@Override
public void removeChannelEventListener()
{
mChannelEventListener = null;
}
/**
* Compares the TO role identifier(s) from each collection for equality
*
* @param collection1 containing a TO identifier
* @param collection2 containing a TO identifier
* @return true if both collections contain a TO identifier and the TO identifiers are the same value
*/
private boolean isSameCall(IdentifierCollection collection1, IdentifierCollection collection2)
{
Identifier toIdentifier1 = getIdentifier(collection1, Role.TO);
Identifier toIdentifier2 = getIdentifier(collection2, Role.TO);
return Objects.equals(toIdentifier1, toIdentifier2);
}
/**
* Retrieves the first identifier with a TO role.
*
* @param collection containing a TO identifier
* @return TO identifier or null
*/
private Identifier getIdentifier(IdentifierCollection collection, Role role)
{
List<Identifier> identifiers = collection.getIdentifiers(role);
if(identifiers.size() >= 1)
{
return identifiers.get(0);
}
return null;
}
/**
* Implements the IDecodeEventProvider interface to provide channel events to an external listener.
*/
@Override
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventListener = listener;
}
/**
* Removes the external decode event listener
*/
@Override
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
{
mDecodeEventListener = null;
}
@Override
public void reset()
{
}
/**
* Starts this traffic channel manager.
*
* Note: for Capacity+ systems, this traffic channel manager will be reused when the current channel is in use and
* a new rest channel is nominated. This traffic channel manager instance will be transferred to the new standard
* channel created to monitor the new rest channel. As such, this manager will have a list of currently allocated
* traffic channels. Broadcast frequency lock requests for each allocated traffic channel frequency so that the
* new rest channel rotation manager doesn't rotate onto frequencies already being monitored as traffic channels.
*/
@Override
public void start()
{
for(Long frequency: mAllocatedTrafficChannelFrequencyMap.keySet())
{
getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(frequency));
}
}
@Override
public void stop()
{
mAvailableTrafficChannelQueue.clear();
List<Channel> channels = new ArrayList<>(mAllocatedTrafficChannelFrequencyMap.values());
//Issue a disable request for each traffic channel
for(Channel channel: channels)
{
broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
}
}
/**
* Monitors channel teardown events to detect when traffic channel processing has ended. Reclaims the
* channel instance for reuse by future traffic channel grants.
*/
public class TrafficChannelTeardownMonitor implements Listener<ChannelEvent>
{
/**
* Removes any call events that are associated with the specified frequency.
* @param frequency that was removed
* @param description to optionally append to the details for the event
*/
private void cleanupCallEvents(long frequency, String description)
{
List<Integer> lsnsToRemove = new ArrayList<>();
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
{
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
{
lsnsToRemove.add(entry.getKey());
}
}
for(Integer lsn: lsnsToRemove)
{
DMRChannelGrantEvent event = mLSNGrantEventMap.remove(lsn);
if(event != null)
{
if(description != null)
{
if(event.getEventDescription() != null)
{
event.setEventDescription(event.getEventDescription() + " - " + description);
}
else
{
event.setEventDescription(description);
}
}
broadcast(event);
}
}
}
@Override
public synchronized void receive(ChannelEvent channelEvent)
{
Channel channel = channelEvent.getChannel();
if(channel.isTrafficChannel() && (channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_STOP ||
channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_START_REJECTED))
{
String optionalDescription = (channelEvent.getEvent() == Event.NOTIFICATION_PROCESSING_START_REJECTED ?
"Rejected" : null);
long frequencyToRemove = 0;
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
{
if(channel == entry.getValue() && entry.getKey() != null)
{
frequencyToRemove = entry.getKey();
cleanupCallEvents(entry.getKey(), optionalDescription);
break;
}
}
if(frequencyToRemove > 0)
{
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToRemove);
//Unlock the frequency in the channel rotation monitor
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove));
}
//Add the traffic channel back to the queue to be reused
if(!mAvailableTrafficChannelQueue.contains(channel))
{
mAvailableTrafficChannelQueue.add(channel);
}
}
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2020 Dennis Sheirer, Zhenyu Mao
*
* 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.dmr;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Decoder Configuration
*/
public class DecodeConfigDMR extends DecodeConfiguration
{
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private boolean mIgnoreDataCalls = true;
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
public DecodeConfigDMR()
{
}
/**
* Overrides the default value to indicate that DMR has two timeslots
*/
@Override
public int getTimeslotCount()
{
return 2;
}
/**
* Timeslot identifiers
*/
@Override
public int[] getTimeslots()
{
int[] timeslots = new int[2];
timeslots[0] = 1;
timeslots[1] = 2;
return timeslots;
}
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
public DecoderType getDecoderType()
{
return DecoderType.DMR;
}
/**
* Indicates if traffic channel grants for data calls should be ignored
*/
@JacksonXmlProperty(isAttribute = true, localName = "ignore_data_calls")
public boolean getIgnoreDataCalls()
{
return mIgnoreDataCalls;
}
/**
* Sets flag to ignore data call traffic channel grants
* @param ignore true to ignore data calls.
*/
public void setIgnoreDataCalls(boolean ignore)
{
mIgnoreDataCalls = ignore;
}
/**
* Traffic channel pool size.
* @return
*/
@JacksonXmlProperty(isAttribute = true, localName = "traffic_channel_pool_size")
public int getTrafficChannelPoolSize()
{
return mTrafficChannelPoolSize;
}
/**
* Sets the traffic channel pool size which is the maximum number of
* simultaneous traffic channels that can be allocated.
*
* This limits the maximum calls so that busy systems won't cause more
* traffic channels to be allocated than the decoder/software/host computer
* can support.
*/
public void setTrafficChannelPoolSize(int size)
{
mTrafficChannelPoolSize = size;
}
/**
* Source channel specification for this decoder
*/
@JsonIgnore
@Override
public ChannelSpecification getChannelSpecification()
{
return new ChannelSpecification(50000.0, 12500, 6500.0, 7200.0);
}
/**
* Timeslot map for this decode configuration.
*
* Note: this is only used for MotoTRBO systems
*/
@JacksonXmlProperty(isAttribute = false, localName = "timeslot")
public List<TimeslotFrequency> getTimeslotMap()
{
return mTimeslotMap;
}
/**
* Sets the timeslot map
* @param timeslots for a MotoTRBO system
*/
public void setTimeslotMap(List<TimeslotFrequency> timeslots)
{
mTimeslotMap = timeslots;
}
/**
* Adds a timeslot frequency to the map
*/
@JsonIgnore
public void addTimeslotFrequency(TimeslotFrequency timeslotFrequency)
{
mTimeslotMap.add(timeslotFrequency);
}
}

View File

@@ -0,0 +1,44 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer, Zhenyu Mao
*
* 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.dmr;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Listener interface to be notified each time a DMR burst has been detected or when sync is or continues to be lost.
*/
public interface IDMRBurstDetectListener
{
/**
* Indicates that a DMR sync has been detected and binary message with the burst contents are ready for processing
* @param binaryMessage burst Binary Message and any bit errors detected in the message
* @param pattern SyncPattern
*/
void burstDetected(CorrectedBinaryMessage binaryMessage, DMRSyncPattern pattern, int timeslot);
/**
* Indicates that sync has been lost on the dibit stream
*
* @param bitsProcessed since the last sync detect
* @param timeslot that was being tracked when the sync loss was detected
*/
void syncLost(int bitsProcessed, int timeslot);
}

View File

@@ -0,0 +1,155 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tracks proper alignment of timeslots into the two bursts of a burst framer.
*/
public class TimeslotAlignmentTracker
{
private final static Logger mLog = LoggerFactory.getLogger(TimeslotAlignmentTracker.class);
private int mBurst1Ts1 = 0;
private int mBurst1Ts2 = 0;
private int mBurst2Ts1 = 0;
private int mBurst2Ts2 = 0;
/**
* Indicates if the track has accumulated enough cach data to accurately declare timeslot alignment. The test
* checks that both timeslots each have different counts for timeslot 0 and 1 and that both timeslots each indicate
* a different dominant timeslot than the other.
*/
public boolean hasSufficientData()
{
return ((mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts2 > mBurst2Ts1) ||
(mBurst1Ts1 < mBurst1Ts2 && mBurst2Ts2 < mBurst2Ts1) ||
(mBurst1Ts1 == 0 && mBurst1Ts2 == 0 && mBurst2Ts1 != mBurst2Ts2) ||
(mBurst1Ts1 != mBurst1Ts2 && mBurst2Ts1 == 0 && mBurst1Ts2 == 0));
}
/**
* Indicates if the timeslots are aligned by inspection of the CACH value for frame 0 and frame 1
*/
public boolean isAligned()
{
return (mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts2 > mBurst2Ts1) ||
(mBurst1Ts1 == 0 && mBurst1Ts2 == 0 && mBurst2Ts2 > mBurst2Ts1) ||
(mBurst1Ts1 > mBurst1Ts2 && mBurst2Ts1 == 0 && mBurst2Ts2 == 0);
}
/**
* Updates the tracker with the cach fields or sync patterns from the current TDMA burst frames
* @param cach1 optional cach that is currently aligned as timeslot 1
* @param cach2 optional cach that is currently aligned as timeslot 2
* @param pattern1 sync pattern for the burst currently aligned as timeslot 1
* @param pattern2 sync pattern for the burst currently aligned as timeslot 2
*/
public void update(CACH cach1, CACH cach2, DMRSyncPattern pattern1, DMRSyncPattern pattern2)
{
if(cach1 != null && cach1.isValid())
{
if(cach1.isTimeslot0())
{
mBurst1Ts1++;
}
else
{
mBurst1Ts2++;
}
}
if(cach2 != null && cach2.isValid())
{
if(cach2.isTimeslot0())
{
mBurst2Ts1++;
}
else
{
mBurst2Ts2++;
}
}
if(pattern1.isDirectMode())
{
switch((pattern1))
{
case DIRECT_MODE_DATA_TIMESLOT_1:
case DIRECT_MODE_VOICE_TIMESLOT_1:
mBurst1Ts1++;
break;
case DIRECT_MODE_DATA_TIMESLOT_2:
case DIRECT_MODE_VOICE_TIMESLOT_2:
mBurst1Ts2++;
break;
}
}
if(pattern2.isDirectMode())
{
switch((pattern2))
{
case DIRECT_MODE_DATA_TIMESLOT_1:
case DIRECT_MODE_VOICE_TIMESLOT_1:
mBurst2Ts1++;
break;
case DIRECT_MODE_DATA_TIMESLOT_2:
case DIRECT_MODE_VOICE_TIMESLOT_2:
mBurst2Ts2++;
break;
}
}
}
/**
* Resets the tracker values.
*/
public void reset()
{
mBurst1Ts1 = 0;
mBurst1Ts2 = 0;
mBurst2Ts1 = 0;
mBurst2Ts2 = 0;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("BURST 1: ").append(mBurst1Ts1).append("/").append(mBurst1Ts2);
sb.append(" BURST 2:").append(mBurst2Ts1).append("/").append(mBurst2Ts2);
if(hasSufficientData())
{
if(isAligned())
{
sb.append(" **ALIGNED**");
}
else
{
sb.append(" >>MIS-ALIGNED<<");
}
}
return sb.toString();
}
}

View File

@@ -0,0 +1,80 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.dmr.channel;
/**
* DMR Channel with Absolute frequency values
*/
public class DMRAbsoluteChannel extends DMRChannel
{
private long mDownlinkFrequency;
private long mUplinkFrequency;
/**
* Constructs an instance
* @param lcn logical channel number
* @param timeslot for the channel
* @param downlinkFrequency frequency in hz
* @param uplinkFrequency frequency in hz
*/
public DMRAbsoluteChannel(int lcn, int timeslot, long downlinkFrequency, long uplinkFrequency)
{
super(lcn, timeslot);
mDownlinkFrequency = downlinkFrequency;
mUplinkFrequency = uplinkFrequency;
}
/**
* Downlink frequency
* @return value in Hertz
*/
@Override
public long getDownlinkFrequency()
{
return mDownlinkFrequency;
}
/**
* Uplink frequency
* @return value in Hertz
*/
@Override
public long getUplinkFrequency()
{
return mUplinkFrequency;
}
/**
* Formatted channel number
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(getValue());
sb.append(" TS:").append(getTimeslot());
sb.append(" ").append(getDownlinkFrequency() / 1E6d);
return sb.toString();
}
}

View File

@@ -0,0 +1,141 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.channel;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.protocol.Protocol;
import org.apache.commons.lang3.Validate;
/**
* Base DMR Channel
*
* Note: timeslots are tracked as 1 and 2
*/
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor
{
private int mTimeslot;
/**
* Constructs an instance
* @param channel number or repeater number, one-based repeater number.
* @param timeslot in range: 1 or 2
*/
public DMRChannel(int channel, int timeslot)
{
super(channel, IdentifierClass.NETWORK, Form.CHANNEL, Role.BROADCAST);
Validate.inclusiveBetween(0, 2, timeslot, "Timeslot must be between 1 and 2");
mTimeslot = timeslot;
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
/**
* Repeater number for the channel.
*/
public int getRepeater()
{
return getValue();
}
/**
* Timeslot for the channel.
* @return timeslot as zero-based index with values in range: 0 or 1.
*/
public int getTimeslot()
{
return mTimeslot;
}
/**
* Logical slot number for this channel. LSN is a 1-based index value where repeater one, timeslot 1 is
* LSN 1, timeslot 2 is LSN 2, etc.
*
* Formula: LSN = ((channel - 1) * 2) + timeslot
*
* @return logical slot number, a 1-based index value
*/
public int getLogicalSlotNumber()
{
int repeater = getRepeater();
if(repeater > 0)
{
return ((repeater - 1) * 2) + getTimeslot();
}
return 0;
}
/**
* Returns an array of length 1 containing this channel's logical slot number
*/
public int[] getLSNArray()
{
int[] logicalSlotNumbers = new int[1];
logicalSlotNumbers[0] = getLogicalSlotNumber();
return logicalSlotNumbers;
}
/**
* Number of timeslots for the DMR channel.
* @return 2 always.
*/
@Override
public int getTimeslotCount()
{
return 2;
}
/**
* Indicates (true) that this is a DMR TDMA channel.
*/
@Override
public boolean isTDMAChannel()
{
return true;
}
/**
* Not implemented
*/
@Override
public int[] getFrequencyBandIdentifiers()
{
return new int[0];
}
/**
* Not implemented.
*/
@Override
public void setFrequencyBand(IFrequencyBand bandIdentifier)
{
throw new IllegalArgumentException("This method is not supported");
}
}

View File

@@ -0,0 +1,94 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.dmr.channel;
/**
* DMR logical channel. This channel uses a logical channel number and a timeslot.
*/
public class DMRLogicalChannel extends DMRChannel
{
private TimeslotFrequency mTimeslotFrequency;
/**
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
* calculated logical slot value for visual compatibility for users.
*
* @param channel number or repeater number
* @param logicalSlotNumber - zero based index.
*/
public DMRLogicalChannel(int channel, int timeslot)
{
super(channel, timeslot);
}
/**
* Downlink frequency
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
*/
@Override
public long getDownlinkFrequency()
{
if(mTimeslotFrequency != null)
{
return mTimeslotFrequency.getDownlinkFrequency();
}
return 0;
}
/**
* Uplink frequency
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
*/
@Override
public long getUplinkFrequency()
{
if(mTimeslotFrequency != null)
{
return mTimeslotFrequency.getUplinkFrequency();
}
return 0;
}
/**
* Sets the timeslot frequency mapping
* @param timeslotFrequency
*/
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
{
mTimeslotFrequency = timeslotFrequency;
}
/**
* Formatted channel number
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("LSN:").append(getLogicalSlotNumber());
sb.append(" LCN:").append(getValue());
sb.append(" TS:").append(getTimeslot());
return sb.toString();
}
}

View File

@@ -0,0 +1,36 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.channel;
import java.util.List;
public interface ITimeslotFrequencyReceiver
{
/**
* Provides the logical slot number(s) that require a matching timeslot frequency mapping
*/
public int[] getLogicalTimeslotNumbers();
/**
* Applies the list of timeslot frequency mappings to the implementer
* @param timeslotFrequencies that match the logical timeslots
*/
public void apply(List<TimeslotFrequency> timeslotFrequencies);
}

View File

@@ -0,0 +1,174 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.channel;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.util.Callback;
/**
* Maps a timeslot number to a pair of channel frequency values
*/
public class TimeslotFrequency
{
private IntegerProperty mNumberProperty = new SimpleIntegerProperty();
private LongProperty mDownlinkFrequencyProperty = new SimpleLongProperty();
private LongProperty mUplinkFrequencyProperty = new SimpleLongProperty();
private DoubleProperty mDownlinkMhzProperty = new SimpleDoubleProperty();
private DoubleProperty mUplinkMhzProperty = new SimpleDoubleProperty();
/**
* Constructs an instance
*/
public TimeslotFrequency()
{
}
/**
* Creates a deep copy of this instance
*/
public TimeslotFrequency copy()
{
TimeslotFrequency copy = new TimeslotFrequency();
copy.setNumber(getNumber());
copy.setDownlinkFrequency(getDownlinkFrequency());
copy.setUplinkFrequency(getUplinkFrequency());
return copy;
}
/**
* Logical Slot Number property
*/
@JsonIgnore
public IntegerProperty getNumberProperty()
{
return mNumberProperty;
}
/**
* Downlink Frequency property
*/
@JsonIgnore
public LongProperty downlinkFrequencyPropertyProperty()
{
return mDownlinkFrequencyProperty;
}
/**
* Uplink Frequency property
*/
@JsonIgnore
public LongProperty uplinkFrequencyProperty()
{
return mUplinkFrequencyProperty;
}
@JsonIgnore
public DoubleProperty getDownlinkMHz()
{
return mDownlinkMhzProperty;
}
@JsonIgnore
public DoubleProperty getUplinkMHz()
{
return mUplinkMhzProperty;
}
/**
* Logical slot number (LSN) as a 1-index based counter
*/
@JacksonXmlProperty(isAttribute = true, localName = "lsn")
public int getNumber()
{
return mNumberProperty.get();
}
/**
* Sets the logical slot number (LSN) as a 1-index start
* @param lsn where LSN 1 is the first slot, 2 the second, etc
*/
public void setNumber(int lsn)
{
mNumberProperty.set(lsn);
}
/**
* Downlink frequency
* @return value in hertz
*/
@JacksonXmlProperty(isAttribute = true, localName = "downlink")
public long getDownlinkFrequency()
{
return mDownlinkFrequencyProperty.get();
}
/**
* Sets the downlink frequency value
* @param downlinkFrequency in hertz
*/
public void setDownlinkFrequency(long downlinkFrequency)
{
mDownlinkFrequencyProperty.set(downlinkFrequency);
getDownlinkMHz().set(downlinkFrequency / 1E6);
}
/**
* Uplink frequency
* @return value in hertz
*/
@JacksonXmlProperty(isAttribute = true, localName = "uplink")
public long getUplinkFrequency()
{
return mUplinkFrequencyProperty.get();
}
/**
* Sets the uplink frequency value
* @param uplinkFrequency in hertz
*/
public void setUplinkFrequency(long uplinkFrequency)
{
mUplinkFrequencyProperty.set(uplinkFrequency);
getUplinkMHz().set(uplinkFrequency / 1E6);
}
@Override
public String toString()
{
return "TIMESLOT LSN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
}
/**
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
*/
public static Callback<TimeslotFrequency,Observable[]> extractor()
{
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyPropertyProperty(),
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
}
}

View File

@@ -0,0 +1,178 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 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.dmr.event;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
/**
* DMR Channel Grant Event.
*
* This event class is send traffic channel grant details to the traffic channel manager to allocate a traffic channel.
*/
public class DMRChannelGrantEvent extends DMRDecodeEvent
{
private boolean mEncrypted;
/**
* Constructs an instance
* @param timestamp for the event
*/
public DMRChannelGrantEvent(long timestamp)
{
super(timestamp);
}
/**
* Indicates if the channel grant was for an encrypted call
*/
public boolean isEncrypted()
{
return mEncrypted;
}
/**
* Sets the encrypte state for the call
* @param encrypted
*/
public void setEncrypted(boolean encrypted)
{
mEncrypted = encrypted;
}
/**
* Creates a new decode event builder with the specified start timestamp.
* @param timeStart for the event
* @return builder
*/
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(long timeStart)
{
return new DMRChannelGrantDecodeEventBuilder(timeStart);
}
/**
* Builder pattern for constructing decode events.
*/
public static class DMRChannelGrantDecodeEventBuilder
{
protected long mTimeStart;
protected long mDuration;
protected String mEventDescription;
protected IdentifierCollection mIdentifierCollection;
protected DMRChannel mChannel;
protected String mDetails;
protected boolean mEncrypted;
/**
* Constructs a builder instance with the specified start time in milliseconds
*
* @param timeStart
*/
public DMRChannelGrantDecodeEventBuilder(long timeStart)
{
mTimeStart = timeStart;
}
/**
* Sets the encrypted state for the channel/timeslot
*/
public DMRChannelGrantDecodeEventBuilder encrypted(boolean encrypted)
{
mEncrypted = encrypted;
return this;
}
/**
* Sets the duration value
* @param duration in milliseconds
*/
public DMRChannelGrantDecodeEventBuilder duration(long duration)
{
mDuration = duration;
return this;
}
/**
* Sets the duration value using the end - start timestamps
* @param timestamp for end of event
*/
public DMRChannelGrantDecodeEventBuilder end(long timestamp)
{
mDuration = timestamp - mTimeStart;
return this;
}
/**
* Sets the channel descriptor for this event
* @param channelDescriptor
*/
public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel)
{
mChannel = channel;
return this;
}
/**
* Sets the event description text
* @param description of the event
*/
public DMRChannelGrantDecodeEventBuilder eventDescription(String description)
{
mEventDescription = description;
return this;
}
/**
* Sets the identifier collection.
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
* alias list configuration name.
*/
public DMRChannelGrantDecodeEventBuilder identifiers(IdentifierCollection identifierCollection)
{
mIdentifierCollection = identifierCollection;
return this;
}
/**
* Sets the details for the event
* @param details
*/
public DMRChannelGrantDecodeEventBuilder details(String details)
{
mDetails = details;
return this;
}
/**
* Builds the decode event
*/
public DMRChannelGrantEvent build()
{
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mTimeStart);
decodeEvent.setChannelDescriptor(mChannel);
decodeEvent.setTimeslot(mChannel.getTimeslot());
decodeEvent.setDetails(mDetails);
decodeEvent.setDuration(mDuration);
decodeEvent.setEventDescription(mEventDescription);
decodeEvent.setIdentifierCollection(mIdentifierCollection);
return decodeEvent;
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 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.dmr.event;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR Decode Event
*/
public class DMRDecodeEvent extends DecodeEvent
{
/**
* Constucts a decode event
* @param start
*/
public DMRDecodeEvent(long start)
{
super(start);
setProtocol(Protocol.DMR);
}
/**
* Creates a new decode event builder with the specified start timestamp.
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
{
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
decodeEventBuilder.protocol(Protocol.DMR);
return decodeEventBuilder;
}
}

View File

@@ -0,0 +1,60 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.identifier;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.location.LocationIdentifier;
import io.github.dsheirer.identifier.location.Point;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR location identifier for user radio reported GPS positions.
*/
public class DMRLocation extends LocationIdentifier
{
/**
* Constructs an instance
*
* @param value location
* @param role to or from
*/
public DMRLocation(Point value, Role role)
{
super(value, role);
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
/**
* Utility method to create a new DMR location
* @param latitude of the position
* @param longitude of the position
* @return constructed DMR location identifier
*/
public static DMRLocation createFrom(double latitude, double longitude)
{
Point point = new Point(latitude, longitude);
return new DMRLocation(point, Role.FROM);
}
}

View File

@@ -0,0 +1,52 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.identifier;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR Network Identifier
*/
public class DMRNetwork extends IntegerIdentifier
{
/**
* Creates an instance
* @param value of the network
*/
public DMRNetwork(int value)
{
super(value, IdentifierClass.NETWORK, Form.NETWORK, Role.BROADCAST);
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
public static DMRNetwork create(int network)
{
return new DMRNetwork(network);
}
}

View File

@@ -0,0 +1,68 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.dmr.identifier;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR Radio Identifier
*/
public class DMRRadio extends RadioIdentifier
{
public DMRRadio(Integer value, Role role)
{
super(value, role);
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
/**
* Creates a DMR TO radio identifier
*/
public static RadioIdentifier createTo(int radioId)
{
return new DMRRadio(radioId, Role.TO);
}
/**
* Creates a DMR FROM radio identifier
*/
public static RadioIdentifier createFrom(int radioId)
{
return new DMRRadio(radioId, Role.FROM);
}
/**
* Creates a DMR ANY radio identifier
*/
public static RadioIdentifier createAny(int radioId)
{
return new DMRRadio(radioId, Role.ANY);
}
}

View File

@@ -0,0 +1,53 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 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.dmr.identifier;
import io.github.dsheirer.identifier.site.SiteIdentifier;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR Radio Site Identifier
*/
public class DMRSite extends SiteIdentifier
{
/**
* Constructs an instance
* @param site id
*/
public DMRSite(int site)
{
super(site);
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
/**
* Utility method to create a site identifier
*/
public static DMRSite create(int site)
{
return new DMRSite(site);
}
}

View File

@@ -0,0 +1,62 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.dmr.identifier;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.protocol.Protocol;
public class DMRTalkgroup extends TalkgroupIdentifier
{
public DMRTalkgroup(Integer value)
{
super(value, Role.TO);
}
public DMRTalkgroup(Integer value, Role role)
{
super(value, role);
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
/**
* Creates a DMR TO talkgroup identifier
*/
public static TalkgroupIdentifier create(int talkgroup)
{
return new DMRTalkgroup(talkgroup);
}
/**
* Creates a DMR talkgroup identifier with ANY role
*/
public static TalkgroupIdentifier createAny(int talkgroup)
{
return new DMRTalkgroup(talkgroup, Role.ANY);
}
}

View File

@@ -0,0 +1,49 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.dmr.identifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR AMBE audio codec tone metadata
*/
public class DMRToneIdentifier extends ToneIdentifier
{
public DMRToneIdentifier(ToneSequence value)
{
super(value);
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
public static DMRToneIdentifier create(ToneSequence value)
{
return new DMRToneIdentifier(value);
}
}

View File

@@ -0,0 +1,40 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.identifier;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR Unit Status
*/
public class DMRUnitStatus extends UnitStatusIdentifier
{
public DMRUnitStatus(int value)
{
super(value, Role.FROM, Protocol.DMR);
}
public static DMRUnitStatus create(int value)
{
return new DMRUnitStatus(value);
}
}

View File

@@ -0,0 +1,187 @@
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
/**
* Common Announcement Channel is a 24-bit sequence that precedes an outbound DMR frame transmitted by a repeater.
*/
public class CACH
{
private static final int CACH_MESSAGE_LENGTH = 24;
private static final int[] INTERLEAVE_MATRIX = new int[]{0, 4, 8, 12, 14, 18, 22, 1, 2, 3, 5, 6, 7, 9, 10, 11, 13,
15, 16, 17, 19, 20, 21, 23};
private static final int INBOUND_CHANNEL_ACCESS_TYPE = 0;
private static final int OUTBOUND_BURST_TIMESLOT = 1;
private static final int[] LINK_CONTROL_START_STOP = new int[]{2, 3};
private static final int[] CACH_CRC = new int[]{4, 5, 6};
private static final int[] CHECKSUMS = new int[]{5, 7, 6, 3};
private static final int PAYLOAD_START = 7;
private static final int PAYLOAD_END = 24;
public enum AccessType {IDLE, BUSY};
private CorrectedBinaryMessage mMessage;
private boolean mValid;
/**
* Constructs an instance. Note: constructor is private. Use the getCACH() method to extract a CACH from a
* raw transmitted DMR burst frame.
*
* @param message containing the deinterleaved and error corrected CACH binary message
* @param valid to indicate if the deinterleaved message passed CRC check
*/
public CACH(CorrectedBinaryMessage message, boolean valid)
{
mMessage = message;
mValid = valid;
}
/**
* Descrambled message bits for this cach
*/
public CorrectedBinaryMessage getMessage()
{
return mMessage;
}
/**
* Indicates if this CACH has the MSB flipped to indicate Hytera XPT cach scrambling.
*/
public boolean hasHyteraCachScrambling()
{
if(!isValid())
{
BinaryMessage copy = getMessage().copy();
copy.flip(0);
if(getCrcChecksum(copy) == 0)
{
return true;
}
}
return false;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
// sb.append(getMessage().toHexString()).append(" ");
// sb.append(getInboundChannelAccessType().name()).append(" ");
sb.append(getLCSS());
// sb.append(" (").append(getTimeslot()).append("|").append(getCrcChecksum(getMessage())).append(")");
// sb.append(" ").append(getPayload().toHexString());
return sb.toString();
}
/**
* Indicates if this cach passes the FEC error correction and indicates that the inbound channel Access Type,
* Timeslot and LCSS information is valid.
*/
public boolean isValid()
{
return mValid;
}
/**
* Indicates the state of the next inbound channel timeslot, IDLE or BUSY
*/
public AccessType getInboundChannelAccessType()
{
if(mMessage.get(INBOUND_CHANNEL_ACCESS_TYPE))
{
return AccessType.BUSY;
}
else
{
return AccessType.IDLE;
}
}
/**
* Link Control or CSBK Start/Stop fragment indicator
*/
public LCSS getLCSS()
{
return LCSS.fromValue(mMessage.getInt(LINK_CONTROL_START_STOP));
}
/**
* Indicates the outbound timeslot for the next frame that follows this CACH, 0 or 1
*/
public int getTimeslot()
{
return mMessage.get(OUTBOUND_BURST_TIMESLOT) ? 1 : 0;
}
/**
* Indicates if the timeslot is 0
*/
public boolean isTimeslot0()
{
return !mMessage.get(OUTBOUND_BURST_TIMESLOT);
}
/**
* Binary Payload (17-bit) message fragment
*/
public BinaryMessage getPayload()
{
return mMessage.getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
/**
* Utility method to create a CACH from a raw transmitted DMR burst frame. Performs deinterleaving and error
* correction.
*
* @return constructed CACH message.
*/
public static CACH getCACH(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage decodedMessage = new CorrectedBinaryMessage(CACH_MESSAGE_LENGTH);
//Deinterleave the transmitted message to create the decoded message
for(int x = 0; x < CACH_MESSAGE_LENGTH; x++)
{
if(message.get(INTERLEAVE_MATRIX[x]))
{
decodedMessage.set(x);
}
}
int checksum = getCrcChecksum(decodedMessage);
// //debug - investigating corruption of CACH, SLC and EMB during encrypted Hytera/MotoTRBO calls.
// BinaryMessage test = message.getSubMessage(0, CACH_MESSAGE_LENGTH);
// System.out.println("--> CACH:" + test.toString() + " DE:" + decodedMessage.toString() + " CHK:" + checksum);
boolean valid = (checksum == 0);
return new CACH(decodedMessage, valid);
}
/**
* Calculates the CRC checksum residual. Calculates what the checkcum should be and XORs this value with the
* transmitted checksum and returns the residual value. A residual of 0 indicates a valid CRC check.
* @param message to check
* @return checsum residual value
*/
public static int getCrcChecksum(BinaryMessage message)
{
//Perform error detection
int checksum = message.getInt(CACH_CRC);
for(int x = 0; x < 4; x++)
{
if(message.get(x))
{
checksum ^= CHECKSUMS[x];
}
}
return checksum;
}
}

View File

@@ -0,0 +1,56 @@
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
/**
* Base DMR Burst Message class
*/
public abstract class DMRBurst extends DMRMessage
{
public static final int PAYLOAD_1_START = 24;
public static final int SYNC_START = 132;
public static final int PAYLOAD_2_START = 180;
private DMRSyncPattern mSyncPattern;
private CACH mCACH;
/**
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
* station frames transmit a Common Announcement Channel (CACH) in the 24-bit prefix, whereas Mobile inbound frames
* do not use the 24-bit prefix.
*
* @param message containing 288-bit DMR message with preliminary bit corrections indicated.
* @param timestamp of the message
*/
public DMRBurst(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
mSyncPattern = syncPattern;
mCACH = cach;
}
/**
* Common Announcement Channel (CACH) message frame. Note: check hasCACH() before accessing this method.
* @return CACH frame or null if this message does not contain a CACH.
*/
public CACH getCACH()
{
return mCACH;
}
/**
* Indicates if this frame contains Common Announcement Channel (CACH) frame data.
*/
public boolean hasCACH()
{
return getSyncPattern().hasCACH();
}
/**
* DMR Sync pattern used by this message
*/
public DMRSyncPattern getSyncPattern()
{
return mSyncPattern;
}
}

View File

@@ -0,0 +1,84 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.protocol.Protocol;
/**
* Base DMR Message
*/
public abstract class DMRMessage extends Message
{
private CorrectedBinaryMessage mCorrectedBinaryMessage;
private boolean mValid = true;
private int mTimeslot;
/**
* Constructs an instance
* @param timestamp for the message
*/
public DMRMessage(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(timestamp);
mCorrectedBinaryMessage = message;
mTimeslot = timeslot;
}
/**
* Message bits
*/
public CorrectedBinaryMessage getMessage()
{
return mCorrectedBinaryMessage;
}
/**
* Indicates if this message is valid
*/
public boolean isValid()
{
return mValid;
}
/**
* Sets the valid flag for this message
*/
public void setValid(boolean valid)
{
mValid = valid;
}
/**
* Timeslot for this message
* @return 0 or 1
*/
public int getTimeslot()
{
return mTimeslot;
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
}

View File

@@ -0,0 +1,86 @@
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.data.DMRDataMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceAMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DMRMessageFactory
{
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageFactory.class);
/**
* Creates a DMR message wrapper for the binary message. DMR message type is specified by the sync pattern.
* @param syncPattern for the message
* @param binaryMessage containing the raw DMR message burst
* @param timestamp for the burst
* @param timeslot for the burst
* @return DMRMessage instance or null for UNKNOWN sync pattern
*/
public static DMRBurst create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach,
long timestamp, int timeslot)
{
switch(syncPattern)
{
case BASE_STATION_VOICE:
case MOBILE_STATION_VOICE:
case BS_VOICE_FRAME_B:
case BS_VOICE_FRAME_C:
case BS_VOICE_FRAME_D:
case BS_VOICE_FRAME_E:
case BS_VOICE_FRAME_F:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, timeslot);
case DIRECT_MODE_VOICE_TIMESLOT_1:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 1);
case DIRECT_MODE_VOICE_TIMESLOT_2:
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 2);
case BASE_STATION_DATA:
case MOBILE_STATION_DATA:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, timeslot);
case DIRECT_MODE_DATA_TIMESLOT_1:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 1);
case DIRECT_MODE_DATA_TIMESLOT_2:
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 2);
case UNKNOWN:
return null;
case MOBILE_STATION_REVERSE_CHANNEL:
case RESERVED:
default:
return new UnknownDMRMessage(syncPattern, binaryMessage, cach, timestamp, timeslot);
}
}
/**
* Creates a voice message from the raw transmitted DMR burst frame
* @param syncPattern for the frame
* @param message containing the DMR burst
* @param cach extracted from the first 24 bits of the message
* @param timestamp the message was received
* @param timeslot for the DMR burst
* @return constructed voice message
*/
private static VoiceMessage createVoiceMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
long timestamp, int timeslot)
{
switch(syncPattern)
{
case BASE_STATION_VOICE:
case MOBILE_STATION_VOICE:
case DIRECT_MODE_VOICE_TIMESLOT_1:
case DIRECT_MODE_VOICE_TIMESLOT_2:
return new VoiceAMessage(syncPattern, message, cach, timestamp, timeslot);
case BS_VOICE_FRAME_B:
case BS_VOICE_FRAME_C:
case BS_VOICE_FRAME_D:
case BS_VOICE_FRAME_E:
case BS_VOICE_FRAME_F:
default:
return new VoiceEMBMessage(syncPattern, message, cach, timestamp, timeslot);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import java.util.Collections;
import java.util.List;
/**
* Unknown DMR message used as a default case when the message type cannot be determined.
*/
public class UnknownDMRMessage extends DMRBurst
{
/**
* Constructs an instance
*
* @param syncPattern
* @param message containing 288-bit DMR message with preliminary bit corrections indicated.
* @param cach
* @param timestamp
* @param timeslot
*/
public UnknownDMRMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, long timestamp, int timeslot)
{
super(syncPattern, message, cach, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(hasCACH())
{
sb.append(getCACH().toString());
}
else
{
sb.append("TS").append(getTimeslot());
}
sb.append(" ").append(getSyncPattern());
sb.append(" UNKNOWN DMR BURST");
sb.append(" ").append(getMessage().toHexString());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,262 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data;
import io.github.dsheirer.bits.BitSetFullException;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.BPTC_196_96;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.edac.trellis.ViterbiDecoder_3_4_DMR;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1Rate;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1_2Rate;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock3_4Rate;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.data.header.ConfirmedDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.DataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.DefinedShortDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.RawShortDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ResponseDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ShortDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.StatusDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for creating data messages that contain a 196-bit BPTC protected message.
*/
public class DMRDataMessageFactory
{
private final static Logger mLog = LoggerFactory.getLogger(DMRDataMessageFactory.class);
private static final ViterbiDecoder_3_4_DMR VITERBI_DECODER = new ViterbiDecoder_3_4_DMR();
/**
* Creates a data message class
* @param pattern for the DMR burst
* @param message DMR burst as transmitted
* @param cach from the DMR burst
* @param timestamp for the message
* @param timeslot for the message
* @return data message instance
*/
public static DataMessage create(DMRSyncPattern pattern, CorrectedBinaryMessage message, CACH cach, long timestamp,
int timeslot)
{
SlotType slotType = SlotType.getSlotType(message);
if(slotType.isValid())
{
switch(slotType.getDataType())
{
case SLOT_IDLE:
return new IDLEMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case CSBK:
return CSBKMessageFactory.create(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case USB_DATA:
CorrectedBinaryMessage usbdPayload = getPayload(message);
switch(USBData.getServiceType(usbdPayload))
{
case LIP_SHORT_LOCATION_REQUEST:
default:
return new USBData(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
}
case MBC_ENC_HEADER:
case MBC_HEADER:
return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case CHANNEL_CONTROL_ENC_HEADER:
case PI_HEADER:
return new HeaderMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case VOICE_HEADER:
return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case DATA_ENC_HEADER:
case DATA_HEADER:
CorrectedBinaryMessage payload = getPayload(message);
int crcBitCount = CRCDMR.correctCCITT80(payload, 0, 80, 0xCCCC);
boolean valid = crcBitCount < 2;
DataPacketFormat dpf = DataHeader.getDataPacketFormat(payload);
switch(dpf)
{
case CONFIRMED_DATA_PACKET:
ConfirmedDataHeader cdh = new ConfirmedDataHeader(pattern, payload, cach, slotType, timestamp, timeslot);
cdh.setValid(valid);
return cdh;
case PROPRIETARY_DATA_PACKET:
Vendor vendor = ProprietaryDataHeader.getVendor(payload);
switch(vendor)
{
case MOTOROLA_CAPACITY_PLUS:
case MOTOROLA_CONNECT_PLUS:
ServiceAccessPoint sap = ProprietaryDataHeader.getServiceAccessPoint(payload);
if(sap == ServiceAccessPoint.SAP_1)
{
MNISProprietaryDataHeader mpdh = new MNISProprietaryDataHeader(pattern, payload,
cach, slotType, timestamp, timeslot);
mpdh.setValid(valid);
return mpdh;
}
else
{
MotorolaProprietaryDataHeader mprdh = new MotorolaProprietaryDataHeader(pattern,
payload, cach, slotType, timestamp, timeslot);
mprdh.setValid(valid);
return mprdh;
}
case HYTERA_68:
HyteraProprietaryDataHeader hpdh = new HyteraProprietaryDataHeader(pattern, payload,
cach, slotType, timestamp, timeslot);
hpdh.setValid(valid);
return hpdh;
default:
ProprietaryDataHeader pdh = new ProprietaryDataHeader(pattern, payload, cach,
slotType, timestamp, timeslot);
pdh.setValid(valid);
return pdh;
}
case RAW_OR_STATUS_SHORT_DATA:
int appendedBlockCount = ShortDataHeader.getAppendedBlocks(payload);
if(appendedBlockCount == 0)
{
StatusDataHeader sdh = new StatusDataHeader(pattern, payload, cach, slotType, timestamp,
timeslot);
sdh.setValid(valid);
return sdh;
}
else
{
RawShortDataHeader rsdh = new RawShortDataHeader(pattern, payload, cach, slotType,
timestamp, timeslot);
rsdh.setValid(valid);
return rsdh;
}
case RESPONSE_PACKET:
ResponseDataHeader rdh = new ResponseDataHeader(pattern, payload, cach, slotType, timestamp,
timeslot);
rdh.setValid(valid);
return rdh;
case DEFINED_SHORT_DATA:
DefinedShortDataHeader dsdh = new DefinedShortDataHeader(pattern, payload, cach,slotType,
timestamp, timeslot);
dsdh.setValid(valid);
return dsdh;
case UNCONFIRMED_DATA_PACKET:
UnconfirmedDataHeader udh = new UnconfirmedDataHeader(pattern, payload, cach, slotType,
timestamp, timeslot);
udh.setValid(valid);
return udh;
case UNIFIED_DATA_TRANSPORT:
//TODO: this will eventually need its own factory
UDTHeader uh = new UDTHeader(pattern, payload, cach, slotType, timestamp, timeslot);
uh.setValid(valid);
return uh;
default:
DataHeader dh = new DataHeader(pattern, payload, cach, slotType, timestamp, timeslot);
dh.setValid(valid);
return dh;
}
case TLC:
return new Terminator(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case RATE_1_OF_2_DATA:
return new DataBlock1_2Rate(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case RATE_3_OF_4_DATA:
return new DataBlock3_4Rate(pattern, getTrellisPayload(message), cach, slotType, timestamp, timeslot);
case RATE_1_DATA:
return new DataBlock1Rate(pattern, message, cach, slotType, timestamp, timeslot);
case MBC_BLOCK:
return new MBCContinuationBlock(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case RESERVED_15:
case UNKNOWN:
return new UnknownDataMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
}
}
return new UnknownDataMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
}
/**
* Decodes a 3/4 rate trellis coded message
* @param message containing trellis coded message
* @return decoded message
*/
private static CorrectedBinaryMessage getTrellisPayload(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage descrambled = extract(message);
return VITERBI_DECODER.decode(descrambled);
}
/**
* De-scramble, decode and error check a BPTC protected raw message and return a 96-bit error corrected
* payload or null.
* @param message
* @return
*/
private static CorrectedBinaryMessage getPayload(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage descrambled = extract(message);
return BPTC_196_96.extract(descrambled);
}
/**
* Extracts the 196-bit message payload from the full DMR burst
* @param message as transmitted
* @return extracted message
*/
private static CorrectedBinaryMessage extract(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage extracted = new CorrectedBinaryMessage(196);
try
{
for(int i = 24; i < 122; i++)
{
extracted.add(message.get(i));
}
for(int i = 190; i < 190 + 98; i++)
{
extracted.add(message.get(i));
}
}
catch(BitSetFullException ex)
{
mLog.error("Error extracting DMR burst payload bits");
}
return extracted;
}
}

View File

@@ -0,0 +1,58 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2020 Zhenyu Mao
*
* 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.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
/**
* Base DMR Data Message
*/
public abstract class DataMessage extends DMRBurst
{
private SlotType mSlotType;
/**
* Constructs an instance.
*
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
* @param message containing extracted 196-bit payload.
* @param cach for the DMR burst
* @param slotType for this data message
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
public DataMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot)
{
super(syncPattern, message, cach, timestamp, timeslot);
mSlotType = slotType;
}
/**
* Slot Type identifies the color code and data type for this data message
*/
public SlotType getSlotType()
{
return mSlotType;
}
}

View File

@@ -0,0 +1,89 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.Collections;
import java.util.List;
/**
* Data Message with Full Link Control Payload.
*/
public abstract class DataMessageWithLinkControl extends DataMessage
{
private LCMessage mLCMessage;
/**
* Constructs an instance.
*
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
* @param message containing extracted 196-bit payload.
* @param cach for the DMR burst
* @param slotType for this data message
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
public DataMessageWithLinkControl(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Access the embedded link control message
*/
public LCMessage getLCMessage()
{
if(mLCMessage == null)
{
boolean isTerminator = getSlotType().getDataType() == DataType.TLC;
mLCMessage = LCMessageFactory.createFull(getMessage(), getTimestamp(), getTimeslot(), isTerminator);
}
return mLCMessage;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(getSlotType());
sb.append(" ").append(getLCMessage());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
if(getLCMessage() != null)
{
return getLCMessage().getIdentifiers();
}
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,62 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2020 Zhenyu Mao
*
* 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.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import java.util.Collections;
import java.util.List;
/**
* Timeslot IDLE message
*/
public class IDLEMessage extends DataMessage
{
/**
* Constructs an instance
* @param syncPattern for the message
* @param message payload
* @param timestamp the message was received
* @param timeslot for the message
*/
public IDLEMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" IDLE");
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,122 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2020 Zhenyu Mao
*
* 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.dmr.message.data;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.Golay24;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
public class SlotType
{
private static final int[] MESSAGE_INDEXES = new int[]{122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 180, 181,
182, 183, 184, 185, 186, 187, 188, 189};
//Note: extracted message bit index values are offset by +4 from the ICD specified index values
private static final int[] COLOR_CODE = new int[]{4, 5, 6, 7};
private static final int[] DATA_TYPE = new int[]{8, 9, 10, 11};
private CorrectedBinaryMessage mMessage;
private boolean mValid;
public SlotType(CorrectedBinaryMessage message)
{
mMessage = message;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("CC:").append(getColorCode());
sb.append(" ").append(getDataType());
return sb.toString();
}
/**
* Binary message bits
*/
private CorrectedBinaryMessage getMessage()
{
return mMessage;
}
/**
* Sets the valid flag for this slot type.
*
* @param valid
*/
void setValid(boolean valid)
{
mValid = valid;
}
/**
* Indicates if this message was successfully error detected and corrected
*/
public boolean isValid()
{
return mValid;
}
/**
* SlotType message extracted from the transmitted message and Golay(24) error detection and correction complete.
*
* Note: this message is extracted with 4 leading/padding bits plus the 20 message bits for an overall length of
* 24 bits to support use of the existing Golay24 error correction utility. All bit index values are incremented
* by an offset of 4 to account for this frame offset.
*/
public static SlotType getSlotType(BinaryMessage message)
{
CorrectedBinaryMessage decodedMessage = new CorrectedBinaryMessage(24);
for(int x = 0; x < MESSAGE_INDEXES.length; x++)
{
if(message.get(MESSAGE_INDEXES[x]))
{
decodedMessage.set(x + 4);
}
}
int errorCount = Golay24.checkAndCorrect(decodedMessage, 0);
decodedMessage.setCorrectedBitCount(errorCount);
SlotType slotType = new SlotType(decodedMessage);
slotType.setValid(errorCount < 3);
return slotType;
}
/**
* Color code for this timeslot.
*
* @return color code: 0 - 15
*/
public int getColorCode()
{
return getMessage().getInt(COLOR_CODE);
}
/**
* Data Type for this data message
*/
public DataType getDataType()
{
return DataType.fromValue(getMessage().getInt(DATA_TYPE));
}
}

View File

@@ -0,0 +1,75 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import java.util.Collections;
import java.util.List;
/**
* Unknown data message
*/
public class UnknownDataMessage extends DataMessage
{
/**
* Constructs an instance.
*
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
* @param message containing extracted 196-bit payload.
* @param cach
* @param slotType
* @param timestamp
* @param timeslot
*/
public UnknownDataMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(hasCACH())
{
sb.append(getCACH());
}
else
{
sb.append("TS").append(getTimeslot());
}
sb.append(" ").append(getSlotType());
sb.append(" ").append(getMessage().toHexString());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,84 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import java.util.Collections;
import java.util.List;
/**
* Base class for data blocks using 1/2, 3/4, and 1/1 trellis coded data payloads
*/
public abstract class DataBlock extends DataMessage
{
/**
* Constructs an instance.
*
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
* @param message containing extracted 196-bit payload.
* @param cach for the DMR burst
* @param slotType for this data message
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
public DataBlock(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Data block payload for confirmed delivery.
*/
public abstract CorrectedBinaryMessage getConfirmedPayload();
/**
* Data block payload for unconfirmed delivery
*/
public abstract CorrectedBinaryMessage getUnConfirmedPayload();
/**
* Serial number for a confirmed data block
*/
public abstract int getDataBlockSerialNumber();
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(" ").append(getSlotType());
sb.append(" ").append(getMessage().toHexString());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,83 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
/**
* Rate 1/1 Data Block
*/
public class DataBlock1Rate extends DataBlock
{
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
private static final int CONFIRMED_PAYLOAD_START = 16;
private static final int UNCONFIRMED_PAYLOAD_START = 0;
private static final int PAYLOAD_END = 192;
/**
* Constructs an instance.
*
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
* @param message containing extracted 196-bit payload.
* @param cach for the DMR burst
* @param slotType for this data message
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
public DataBlock1Rate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
//TODO: check CRC for data block serial number
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("RATE 1/1 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
return sb.toString();
}
@Override
public int getDataBlockSerialNumber()
{
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
}
@Override
public CorrectedBinaryMessage getConfirmedPayload()
{
return getMessage().getSubMessage(CONFIRMED_PAYLOAD_START, PAYLOAD_END);
}
@Override
public CorrectedBinaryMessage getUnConfirmedPayload()
{
return getMessage().getSubMessage(UNCONFIRMED_PAYLOAD_START, PAYLOAD_END);
}
}

View File

@@ -0,0 +1,83 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
/**
* Rate 1/2 BPTC 196 Data Block
*/
public class DataBlock1_2Rate extends DataBlock
{
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
private static final int CONFIRMED_PAYLOAD_START = 16;
private static final int UNCONFIRMED_PAYLOAD_START = 0;
private static final int PAYLOAD_END = 96;
/**
* Constructs an instance.
*
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
* @param message containing extracted 196-bit payload.
* @param cach for the DMR burst
* @param slotType for this data message
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
public DataBlock1_2Rate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
//TODO: check CRC for data block serial number
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("RATE 1/2 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
return sb.toString();
}
@Override
public int getDataBlockSerialNumber()
{
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
}
@Override
public CorrectedBinaryMessage getConfirmedPayload()
{
return getMessage().getSubMessage(CONFIRMED_PAYLOAD_START, PAYLOAD_END);
}
@Override
public CorrectedBinaryMessage getUnConfirmedPayload()
{
return getMessage().getSubMessage(UNCONFIRMED_PAYLOAD_START, PAYLOAD_END);
}
}

View File

@@ -0,0 +1,84 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
/**
* Rate 3/4 Trellis Coded Data Block
*/
public class DataBlock3_4Rate extends DataBlock
{
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
private static final int CONFIRMED_PAYLOAD_START = 16;
private static final int UNCONFIRMED_PAYLOAD_START = 0;
private static final int PAYLOAD_END = 144;
/**
* Constructs an instance.
*
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
* @param message containing extracted 196-bit payload.
* @param cach for the DMR burst
* @param slotType for this data message
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
public DataBlock3_4Rate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
//TODO: check CRC for data block serial number
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("RATE 3/4 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
return sb.toString();
}
@Override
public int getDataBlockSerialNumber()
{
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
}
@Override
public CorrectedBinaryMessage getConfirmedPayload()
{
return getMessage().getSubMessage(CONFIRMED_PAYLOAD_START, PAYLOAD_END);
}
@Override
public CorrectedBinaryMessage getUnConfirmedPayload()
{
return getMessage().getSubMessage(UNCONFIRMED_PAYLOAD_START, PAYLOAD_END);
}
}

View File

@@ -0,0 +1,65 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.block;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
/**
* Unknown DMR data block
*/
public class UnknownDataBlock extends DataBlock
{
/**
* Constructs an instance.
*
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
* @param message containing extracted 196-bit payload.
* @param cach for the DMR burst
* @param slotType for this data message
* @param timestamp message was received
* @param timeslot for the DMR burst
*/
public UnknownDataBlock(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public CorrectedBinaryMessage getConfirmedPayload()
{
return getMessage();
}
@Override
public CorrectedBinaryMessage getUnConfirmedPayload()
{
return getMessage();
}
@Override
public int getDataBlockSerialNumber()
{
return 0;
}
}

View File

@@ -0,0 +1,166 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2020 Dennis Sheirer, Zhenyu Mao
*
* 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.dmr.message.data.csbk;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
/**
* Control Signalling Block (CSBK) Message
*
* ETSI 102 361-1 7.2.0
*/
public abstract class CSBKMessage extends DataMessage
{
protected static final int CSBK_CRC_MASK = 0xA5A5;
protected static final int MBC_HEADER_CRC_MASK = 0xAAAA;
protected static final int MBC_LAST_BLOCK_CRC_MASK = 0x0;
private static final int LAST_BLOCK = 0;
private static final int PROTECT_FLAG = 1;
private static final int[] OPCODE = new int[]{2, 3, 4, 5, 6, 7};
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
/**
* Constructs an instance
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CSBKMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Checks CRC and sets the message valid flag according to the results.
*/
public void checkCRC()
{
int correctedBitCount = CRCDMR.correctCCITT80(getMessage(), 0, 80, CSBK_CRC_MASK);
//Set message valid flag according to the corrected bit count for the CRC protected message
setValid(correctedBitCount < 2);
}
/**
* Checks crc for multi-block CSBK where the continuation block is an absolute channel parameters block
*
* Note: this method assumes that there is just 1 continuation block and that block is used to carry
* the absolute channel parameters structure. If future implementations use more than 1 MBC continuation
* block, then the CRC check has to be performed across all of the continuation blocks where the CRC checksum is
* contained in the final continuation block.
*/
protected void checkMultiBlockCRC(AbsoluteChannelParameters absoluteChannelParameters)
{
int headerCorrectedBitCount = CRCDMR.correctCCITT80(getMessage(), 0, 80, MBC_HEADER_CRC_MASK);
int continuationCorrectedBitCount = 0;
if(absoluteChannelParameters != null)
{
continuationCorrectedBitCount = CRCDMR.correctCCITT80(absoluteChannelParameters.getMessage(),
0, 80, MBC_LAST_BLOCK_CRC_MASK);
}
//Set message valid flag according to the corrected bit count for the CRC protected messages
setValid(headerCorrectedBitCount < 2 && continuationCorrectedBitCount < 2);
}
/**
* Indicates if this is the last/final block of a multi-block message fragment sequence.
*/
public boolean isLastBlock()
{
return getMessage().get(LAST_BLOCK);
}
/**
* Indicates if this message is encrypted.
*/
public boolean isEncrypted()
{
return getMessage().get(PROTECT_FLAG);
}
/**
* Utility method to lookup the opcode from a CSBK message
* @param message containing CSBK bits
* @return opcode
*/
public static Opcode getOpcode(BinaryMessage message)
{
return Opcode.fromValue(message.getInt(OPCODE), getVendor(message));
}
/**
* Opcode for this CSBK message
*/
public Opcode getOpcode()
{
return getOpcode(getMessage());
}
/**
* Opcode numeric value
*/
protected int getOpcodeValue()
{
return getMessage().getInt(OPCODE);
}
/**
* Utility method to lookup the vendor from a CSBK message
* @param message containing CSBK bits
* @return vendor
*/
public static Vendor getVendor(BinaryMessage message)
{
return Vendor.fromValue(message.getInt(VENDOR));
}
/**
* Vendor for this message
*/
public Vendor getVendor()
{
return getVendor(getMessage());
}
/**
* Numerical value for the vendor
*/
protected int getVendorID()
{
return getMessage().getInt(VENDOR);
}
}

View File

@@ -0,0 +1,426 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAdjacentSiteInformation;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusCSBKO_60;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowAnnouncement;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusNeighbors;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusPreamble;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusSystemStatus;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusCSBKO_16;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusDataChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusDataRevertWindowAnnouncement;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusDataRevertWindowGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusNeighborReport;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusOTAAnnouncement;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusRegistrationRequest;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusRegistrationResponse;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusTalkgroupAffiliation;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusTerminateChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Aloha;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Clear;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.MoveTSCC;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.Acknowledge;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.AcknowledgeStatus;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.RegistrationAccepted;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy.Ahoy;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy.AuthenticateRegisterRadioCheck;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy.CancelCall;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy.ServiceRadioCheck;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy.StunReviveKill;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.ahoy.UnknownAhoy;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.AdjacentSiteInformation;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.AnnounceChannelFrequency;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.AnnounceWithdrawTSCC;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.Announcement;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.CallTimerParameters;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.LocalTime;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.MassRegistration;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.VoteNowAdvice;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.BroadcastTalkgroupVoiceChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.DuplexPrivateDataChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.DuplexPrivateVoiceChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.PrivateDataChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.PrivateVoiceChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.TalkgroupDataChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.TalkgroupVoiceChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
import java.util.List;
/**
* Factory for creating DMR CSBK and Multi-Block CSBK messages
*/
public class CSBKMessageFactory
{
/**
* Creates a CSBK Message
* @param pattern that was transmitted
* @param message contents
* @param cach instance
* @param slotType instance
* @param timestamp of the message
* @param timeslot the message was transmitted on
* @return constructed CSBK mesage
*/
public static CSBKMessage create(DMRSyncPattern pattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot)
{
CSBKMessage csbk = null;
if(message != null)
{
Opcode opcode = CSBKMessage.getOpcode(message);
switch(opcode)
{
case STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_TSCC:
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC:
case STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD:
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD:
switch(Acknowledge.getReason(message))
{
case TS_REGISTRATION_ACCEPTED:
csbk = new RegistrationAccepted(pattern, message, cach, slotType, timestamp, timeslot);
break;
case TS_ACCEPTED_FOR_STATUS_POLLING_SERVICE:
csbk = new AcknowledgeStatus(pattern, message, cach, slotType, timestamp, timeslot);
break;
default:
csbk = new Acknowledge(pattern, message, cach, slotType, timestamp, timeslot);
break;
}
break;
case STANDARD_AHOY:
ServiceKind serviceKind = Ahoy.getServiceKind(message);
switch(serviceKind)
{
case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE:
csbk = new AuthenticateRegisterRadioCheck(pattern, message, cach, slotType, timestamp, timeslot);
break;
case CANCEL_CALL_SERVICE:
csbk = new CancelCall(pattern, message, cach, slotType, timestamp, timeslot);
break;
case SUPPLEMENTARY_SERVICE:
csbk = new StunReviveKill(pattern, message, cach, slotType, timestamp, timeslot);
break;
case FULL_DUPLEX_MS_TO_MS_PACKET_CALL_SERVICE:
case FULL_DUPLEX_MS_TO_MS_VOICE_CALL_SERVICE:
case INDIVIDUAL_VOICE_CALL_SERVICE:
case INDIVIDUAL_PACKET_CALL_SERVICE:
case INDIVIDUAL_UDT_SHORT_DATA_CALL_SERVICE:
case TALKGROUP_PACKET_CALL_SERVICE:
case TALKGROUP_UDT_SHORT_DATA_CALL_SERVICE:
case TALKGROUP_VOICE_CALL_SERVICE:
csbk = new ServiceRadioCheck(pattern, message, cach, slotType, timestamp, timeslot);
break;
default:
csbk = new UnknownAhoy(pattern, message, cach, slotType, timestamp, timeslot);
break;
}
break;
case STANDARD_ALOHA:
csbk = new Aloha(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_ANNOUNCEMENT:
switch(Announcement.getAnnouncementType(message))
{
case ADJACENT_SITE_INFORMATION:
csbk = new AdjacentSiteInformation(pattern, message, cach, slotType, timestamp, timeslot);
break;
case ANNOUNCE_OR_WITHDRAW_TSCC:
csbk = new AnnounceWithdrawTSCC(pattern, message, cach, slotType, timestamp, timeslot);
break;
case CALL_TIMER_PARAMETERS:
csbk = new CallTimerParameters(pattern, message, cach, slotType, timestamp, timeslot);
break;
case LOCAL_TIME:
csbk = new LocalTime(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MASS_REGISTRATION:
csbk = new MassRegistration(pattern, message, cach, slotType, timestamp, timeslot);
break;
case VOTE_NOW_ADVICE:
csbk = new VoteNowAdvice(pattern, message, cach, slotType, timestamp, timeslot);
break;
default:
csbk = new Announcement(pattern, message, cach, slotType, timestamp, timeslot);
break;
}
break;
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
csbk = new BroadcastTalkgroupVoiceChannelGrant(pattern, message, cach, slotType, timestamp,
timeslot);
break;
case STANDARD_CLEAR:
csbk = new Clear(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT:
csbk = new DuplexPrivateDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
csbk = new DuplexPrivateVoiceChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM:
csbk = new PrivateDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
csbk = new PrivateVoiceChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
csbk = new TalkgroupDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
csbk = new TalkgroupVoiceChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_MOVE_TSCC:
csbk = new MoveTSCC(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_PREAMBLE:
csbk = new Preamble(pattern, message, cach, slotType, timestamp, timeslot);
break;
case HYTERA_08_ANNOUNCEMENT:
case HYTERA_68_ANNOUNCEMENT:
AnnouncementType announcementType = HyteraAnnouncement.getAnnouncementType(message);
switch(announcementType)
{
case ADJACENT_SITE_INFORMATION:
csbk = new HyteraAdjacentSiteInformation(pattern, message, cach, slotType, timestamp, timeslot);
break;
default:
csbk = new HyteraAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
break;
}
break;
case HYTERA_XPT_PREAMBLE:
csbk = new HyteraXPTPreamble(pattern, message, cach, slotType, timestamp, timeslot);
break;
case HYTERA_XPT_SITE_STATE:
csbk = new HyteraXPTSiteState(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPMAX_ALOHA:
csbk = new CapacityMaxAloha(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPPLUS_NEIGHBOR_REPORT:
csbk = new CapacityPlusNeighbors(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPPLUS_PREAMBLE:
csbk = new CapacityPlusPreamble(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPPLUS_SYSTEM_STATUS:
csbk = new CapacityPlusSystemStatus(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPPLUS_DATA_WINDOW_ANNOUNCEMENT:
csbk = new CapacityPlusDataRevertWindowAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT:
csbk = new CapacityPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_CSBKO_10:
csbk = new ConnectPlusOTAAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_CSBKO_16:
csbk = new ConnectPlusCSBKO_16(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
csbk = new ConnectPlusRegistrationRequest(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
csbk = new ConnectPlusRegistrationResponse(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
csbk = new ConnectPlusTalkgroupAffiliation(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPPLUS_CSBKO_60:
csbk = new CapacityPlusCSBKO_60(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT:
csbk = new ConnectPlusDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
csbk = new ConnectPlusNeighborReport(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_TERMINATE_CHANNEL_GRANT:
csbk = new ConnectPlusTerminateChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
csbk = new ConnectPlusVoiceChannelUser(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT:
csbk = new ConnectPlusDataRevertWindowAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CONPLUS_DATA_WINDOW_GRANT:
csbk = new ConnectPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
}
if(csbk == null)
{
csbk = new UnknownCSBKMessage(pattern, message, cach, slotType, timestamp, timeslot);
}
}
//Check CRC and set valid flag
if(csbk != null)
{
csbk.checkCRC();
}
return csbk;
}
/**
* Creates a multi-block CSBK message
* @param header for the Multi-Block CSBK (MBC)
* @param continuationBlocks that form the rest of the message
* @return message
*/
public static CSBKMessage create(MBCHeader header, List<MBCContinuationBlock> continuationBlocks)
{
CSBKMessage csbk;
Opcode opcode = header.getOpcode();
switch(opcode)
{
case STANDARD_ANNOUNCEMENT:
AnnouncementType announcementType = Announcement.getAnnouncementType(header.getMessage());
switch(announcementType)
{
case ADJACENT_SITE_INFORMATION:
csbk = new AdjacentSiteInformation(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case ANNOUNCE_OR_WITHDRAW_TSCC:
csbk = new AnnounceWithdrawTSCC(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case CHANNEL_FREQUENCY_ANNOUNCEMENT:
csbk = new AnnounceChannelFrequency(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case VOTE_NOW_ADVICE:
csbk = new VoteNowAdvice(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
default:
csbk = new UnknownMultiCSBK(header, continuationBlocks);
break;
}
break;
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
csbk = new BroadcastTalkgroupVoiceChannelGrant(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case STANDARD_CLEAR:
csbk = new Clear(header.getSyncPattern(), header.getMessage(), header.getCACH(), header.getSlotType(),
header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT:
csbk = new DuplexPrivateDataChannelGrant(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
csbk = new DuplexPrivateVoiceChannelGrant(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case STANDARD_MOVE_TSCC:
csbk = new MoveTSCC(header.getSyncPattern(), header.getMessage(), header.getCACH(), header.getSlotType(),
header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM:
csbk = new PrivateDataChannelGrant(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
csbk = new PrivateVoiceChannelGrant(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
csbk = new TalkgroupDataChannelGrant(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
csbk = new TalkgroupVoiceChannelGrant(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
case HYTERA_08_ANNOUNCEMENT:
case HYTERA_68_ANNOUNCEMENT:
AnnouncementType hyteraAnnouncementType = HyteraAnnouncement.getAnnouncementType(header.getMessage());
switch(hyteraAnnouncementType)
{
case ADJACENT_SITE_INFORMATION:
csbk = new HyteraAdjacentSiteInformation(header.getSyncPattern(), header.getMessage(), header.getCACH(),
header.getSlotType(), header.getTimestamp(), header.getTimeslot(), getBlock1(continuationBlocks));
break;
default:
csbk = new UnknownMultiCSBK(header, continuationBlocks);
break;
}
break;
default:
csbk = new UnknownMultiCSBK(header, continuationBlocks);
break;
}
//Check CRC and set valid flag
if(csbk != null)
{
csbk.checkCRC();
}
return csbk;
}
/**
* Extracts the first block from the list
* @param blocks containing zero or more blocks
* @return first block or null
*/
private static MBCContinuationBlock getBlock1(List<MBCContinuationBlock> blocks)
{
if(blocks != null && blocks.size() >= 1)
{
return blocks.get(0);
}
return null;
}
}

View File

@@ -0,0 +1,203 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.TreeMap;
/**
* Control Signalling Block (CSBK) Opcode enumeration
*
* ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B
* ETSI TS 102 361-4 DMR Trunking Protocol, Annex B
*/
public enum Opcode
{
STANDARD_FEATURE_NOT_SUPPORTED(Vendor.STANDARD, 3, "FEATURE NOT SUPPORTED"),
STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST(Vendor.STANDARD, 4, "UNIT TO UNIT VOICE SERVICE REQUEST"),
STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_RESPONSE(Vendor.STANDARD, 5, "UNIT TO UNIT VOICE SERVICE RESPONSE"),
STANDARD_CHANNEL_TIMING(Vendor.STANDARD, 7, "CHANNEL TIMING"),
STANDARD_ALOHA(Vendor.STANDARD, 25, "ALOHA"),
STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER(Vendor.STANDARD, 26, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_AHOY(Vendor.STANDARD, 28, "AHOY"),
STANDARD_ACTIVATION(Vendor.STANDARD, 30, "ACTIVATION"),
STANDARD_RANDOM_ACCESS_SERVICE_REQUEST(Vendor.STANDARD, 31, "RANDOM ACCESS SERVICE REQUEST"),
STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC(Vendor.STANDARD, 32, "ACKNOWLEDGE RESPONSE OUTBOUND TSCC"),
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_TSCC(Vendor.STANDARD, 33, "ACKNOWLEDGE RESPONSE INBOUND TSCC"),
STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD(Vendor.STANDARD, 34, "ACKNOWLEDGE RESPONSE OUTBOUND PAYLOAD"),
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD(Vendor.STANDARD, 35, "ACKNOWLEDGE RESPONSE INBOUND PAYLOAD"),
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER(Vendor.STANDARD, 36, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER(Vendor.STANDARD, 37, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_ANNOUNCEMENT(Vendor.STANDARD, 40, "ANNOUNCEMENT"),
STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE(Vendor.STANDARD, 38, "NEGATIVE ACKNOWLEDGE RESPONSE"),
STANDARD_MAINTENANCE(Vendor.STANDARD, 42, "MAINTENANCE"),
STANDARD_CLEAR(Vendor.STANDARD, 46, "CLEAR"),
STANDARD_PROTECT(Vendor.STANDARD, 47, "PROTECT"),
STANDARD_PRIVATE_VOICE_CHANNEL_GRANT(Vendor.STANDARD, 48, "PRIVATE VOICE CHANNEL GRANT"),
STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT(Vendor.STANDARD, 49, "TALKGROUP VOICE CHANNEL GRANT"),
STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT(Vendor.STANDARD, 50, "BROADCAST TALKGROUP VOICE CHANNEL GRANT"),
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM(Vendor.STANDARD, 51, "PRIVATE DATA CHANNEL GRANT SINGLE ITEM"),
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM(Vendor.STANDARD, 52, "TALKGROUP DATA CHANNEL GRANT SINGLE ITEM"),
STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT(Vendor.STANDARD, 53, "DUPLEX PRIVATE VOICE CHANNEL GRANT"),
STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT(Vendor.STANDARD, 54, "DUPLEX PRIVATE DATA CHANNEL GRANT"),
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM(Vendor.STANDARD, 55, "PRIVATE DATA CHANNEL GRANT MULTI ITEM"),
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM(Vendor.STANDARD, 56, "TALKGROUP DATA CHANNEL GRANT MULTI ITEM"),
STANDARD_MOVE_TSCC(Vendor.STANDARD, 57, "MOVE TSCC"),
STANDARD_PREAMBLE(Vendor.STANDARD, 61, "PREAMBLE"),
MOTOROLA_CONPLUS_NEIGHBOR_REPORT(Vendor.MOTOROLA_CONNECT_PLUS, 1, "NEIGHBOR REPORT"),
MOTOROLA_CONPLUS_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CONNECT_PLUS, 3, "VOICE CHANNEL USER"),
MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 6, "DATA CHANNEL GRANT"),
MOTOROLA_CONPLUS_CSBKO_10(Vendor.MOTOROLA_CONNECT_PLUS, 10, "CSBKO 10"),
MOTOROLA_CONPLUS_TERMINATE_CHANNEL_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 12, "TERMINATE CHANNEL GRANT"),
MOTOROLA_CONPLUS_CSBKO_16(Vendor.MOTOROLA_CONNECT_PLUS, 16, "CSBKO 16"),
MOTOROLA_CONPLUS_REGISTRATION_REQUEST(Vendor.MOTOROLA_CONNECT_PLUS, 17, "REGISTRATION REQUEST"),
MOTOROLA_CONPLUS_REGISTRATION_RESPONSE(Vendor.MOTOROLA_CONNECT_PLUS, 18, "REGISTRATION RESPONSE"),
MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION(Vendor.MOTOROLA_CONNECT_PLUS, 24, "TALKGROUP AFFILIATION"),
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT(Vendor.MOTOROLA_CONNECT_PLUS, 28, "ENHANCED DATA REVERT WINDOW ANNOUNCEMENT"),
MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"),
//TODO: Capacity Max (Tier III) also supports Enhanced Data Revert channels with windowing ... they either
//TODO: use CSBKO 41 and 42, or they have a different pair of messages.
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"),
MOTOROLA_CAPPLUS_CALL_ALERT_ACK(Vendor.MOTOROLA_CAPACITY_PLUS, 32, "CALL ALERT ACK"),
MOTOROLA_CAPPLUS_DATA_WINDOW_ANNOUNCEMENT(Vendor.MOTOROLA_CAPACITY_PLUS, 41, "ENHANCED DATA REVERT WINDOW ANNOUNCEMENT"),
MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CAPACITY_PLUS, 42, "ENHANCED DATA REVERT WINDOW GRANT"),
MOTOROLA_CAPPLUS_NEIGHBOR_REPORT(Vendor.MOTOROLA_CAPACITY_PLUS, 59, "NEIGHBOR REPORT"),
MOTOROLA_CAPPLUS_CSBKO_60(Vendor.MOTOROLA_CAPACITY_PLUS, 60, "CSBKO 60"),
MOTOROLA_CAPPLUS_PREAMBLE(Vendor.MOTOROLA_CAPACITY_PLUS, 61, "PREAMBLE"),
MOTOROLA_CAPPLUS_SYSTEM_STATUS(Vendor.MOTOROLA_CAPACITY_PLUS, 62, "SYSTEM STATUS"),
HYTERA_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA XPT SITE STATE"),
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "T-III 08 ANNOUNCEMENT"),
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "T-III 68 ANNOUNCEMENT"),
//Opcode 54 and/or 55 - See patent on Hytera call forwarding
HYTERA_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA XPT PREAMBLE"),
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
private Vendor mVendor;
private int mValue;
private String mLabel;
Opcode(Vendor vendor, int value, String label)
{
mVendor = vendor;
mValue = value;
mLabel = label;
}
/**
* Vendor for this opcode
*/
public Vendor getVendor()
{
return mVendor;
}
/**
* Numeric value for the opcode
*/
public int getValue()
{
return mValue;
}
/**
* Pretty label for the opcode
*/
public String getLabel()
{
return mLabel;
}
@Override
public String toString()
{
return getLabel();
}
/**
* Indicates if this opcode is a data channel grant opcode
*/
public boolean isDataChannelGrantOpcode()
{
return DATA_CHANNEL_GRANT_OPCODES.contains(this);
}
/**
* Data Channel Grant opcodes
*/
public static final EnumSet<Opcode> DATA_CHANNEL_GRANT_OPCODES = EnumSet.of(
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM,
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM,
STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT,
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM,
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM);
/**
* Lookup map of vendors and opcode value to opcodes
*/
private static final Map<Vendor,Map<Integer,Opcode>> LOOKUP_MAP = new EnumMap<>(Vendor.class);
static
{
for(Opcode opcode : Opcode.values())
{
if(LOOKUP_MAP.containsKey(opcode.getVendor()))
{
LOOKUP_MAP.get(opcode.getVendor()).put(opcode.getValue(), opcode);
}
else
{
Map<Integer,Opcode> map = new TreeMap<>();
map.put(opcode.getValue(), opcode);
LOOKUP_MAP.put(opcode.getVendor(), map);
}
}
}
/**
* Lookup the opcode from the value for the specified vendor
*/
public static Opcode fromValue(int value, Vendor vendor)
{
if(LOOKUP_MAP.containsKey(vendor))
{
Map<Integer,Opcode> opcodeMap = LOOKUP_MAP.get(vendor);
if(opcodeMap.containsKey(value))
{
return opcodeMap.get(value);
}
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,97 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import java.util.Collections;
import java.util.List;
/**
* Unknown CSBK Message
*/
public class UnknownCSBKMessage extends CSBKMessage
{
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public UnknownCSBKMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CSBK *UNKNOWN*");
Vendor vendor = getVendor();
if(vendor == Vendor.UNKNOWN)
{
sb.append(" VENDOR:").append(getVendorID());
}
else if(vendor != Vendor.STANDARD)
{
sb.append(" ").append(vendor);
}
Opcode opcode = getOpcode();
if(opcode != Opcode.UNKNOWN)
{
sb.append(" ").append(opcode);
}
else
{
sb.append(" UNKNOWN OPCODE:").append(getOpcodeValue());
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,94 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.AdjacentSiteInformation;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
/**
* Hytera Neighbor/Adjacent Site Information
*/
public class HyteraAdjacentSiteInformation extends AdjacentSiteInformation
{
/**
* Constructs a multi-block MBC instance
* @param syncPattern for the burst
* @param message from the burst
* @param cach from the burst
* @param slotType for the burst
* @param timestamp of the burst
* @param timeslot of the burst
* @param multiBlock containing absolute frequency parameters
*/
public HyteraAdjacentSiteInformation(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
SlotType slotType, long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot, multiBlock);
}
/**
* Constructs a single-block CSBK instance
* @param syncPattern for the burst
* @param message from the burst
* @param cach from the burst
* @param slotType for the burst
* @param timestamp of the burst
* @param timeslot of the burst
*/
public HyteraAdjacentSiteInformation(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA NEIGHBOR ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getNeighborSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getNeighborSystemIdentityCode().getSite());
if(hasAbsoluteChannelParameters())
{
sb.append(" CHAN:").append(getAbsoluteChannelParameters().getChannel());
sb.append(" CC:").append(getAbsoluteChannelParameters().getColorCode());
}
else
{
sb.append(" CHAN:").append(getNeighborChannelNumber());
}
sb.append(" THIS NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -0,0 +1,156 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera DMR Tier III - Announcement Message
*/
public class HyteraAnnouncement extends CSBKMessage
{
private static final int[] ANNOUNCEMENT_TYPE = new int[]{16, 17, 18, 19, 20};
private static final int PARAMS_1_START = 21;
private static final int PARAMS_1_END = 35;
private static final int[] PARAMS_1 = new int[]{21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34};
private static final int REGISTRATION_REQUIRED_FLAG = 35;
private static final int[] BACKOFF = new int[]{36, 37, 38, 39};
private static final int SYSTEM_IDENTITY_CODE_OFFSET = 40;
private static final int PARAMS_2_START = 56;
private static final int PARAMS_2_END = 80;
private static final int[] PARAMS_2 = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
private SystemIdentityCode mSystemIdentityCode;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public HyteraAnnouncement(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA ").append(getAnnouncementType());
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
sb.append(" P1:").append(getParams1().toHexString());
sb.append(" P2:").append(getParams2().toHexString());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public BinaryMessage getParams1()
{
return getMessage().getSubMessage(PARAMS_1_START, PARAMS_1_END);
}
public BinaryMessage getParams2()
{
return getMessage().getSubMessage(PARAMS_2_START, PARAMS_2_END);
}
/**
* Indicates if registration is required on this site
*/
public boolean isRegistrationRequired()
{
return getMessage().get(REGISTRATION_REQUIRED_FLAG);
}
/**
* Backoff value
*/
public int getBackoff()
{
return getMessage().getInt(BACKOFF);
}
/**
* System Identity Code structure
*/
public SystemIdentityCode getSystemIdentityCode()
{
if(mSystemIdentityCode == null)
{
mSystemIdentityCode = new SystemIdentityCode(getMessage(), SYSTEM_IDENTITY_CODE_OFFSET, true);
}
return mSystemIdentityCode;
}
/**
* Announcement message type for this message
*/
public AnnouncementType getAnnouncementType()
{
return getAnnouncementType(getMessage());
}
/**
* Utility to get announcement message type for the specified message
*/
public static AnnouncementType getAnnouncementType(CorrectedBinaryMessage message)
{
return AnnouncementType.fromValue(message.getInt(ANNOUNCEMENT_TYPE));
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,188 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera Preamble
*/
public class HyteraXPTPreamble extends Preamble
{
private static final int[] FREE_REPEATER = new int[]{32, 33, 34, 35};
private static final int[] PRIORITY_REPEATER = new int[]{36, 37, 38, 39};
private static final int[] TARGET_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] PRIORITY_CALL_HASHED_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] SOURCE_ADDRESS = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private IntegerIdentifier mTargetAddress;
private RadioIdentifier mSourceAddress;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public HyteraXPTPreamble(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CSBK PREAMBLE FM:").append(getSourceAddress());
sb.append(" TO:").append(getTargetAddress());
sb.append(isCSBKPreamble() ? " CSBK" : " DATA");
sb.append(" BLOCKS TO FOLLOW:").append(getBlocksToFollow());
if(isAllChannelsBusy())
{
sb.append(" ALL REPEATERS BUSY");
}
else
{
sb.append(" FREE REPEATER:").append(getFreeRepeater());
}
if(hasPriorityCall())
{
sb.append(" PRIORITY CALL FOR:").append(getPriorityCallHashedAddress());
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Indicates if all free repreaters are busy
*/
public boolean isAllChannelsBusy()
{
return getFreeRepeater() == 0;
}
/**
* Free repeater number.
*
* @return repeater, or 0 for all repeaters busy
*/
public int getFreeRepeater()
{
return getMessage().getInt(FREE_REPEATER);
}
/**
* Indicates if there is a priority call on another repeater channel that users should monitor when their
* talkgroup matches the priority call hashed address
*/
public boolean hasPriorityCall()
{
return getPriorityCallRepeater() > 0;
}
/**
* Indicates the repeater number that is forwarding a priority call
*/
public int getPriorityCallRepeater()
{
return getMessage().getInt(PRIORITY_REPEATER);
}
/**
* Hashed address of priority call
*/
public String getPriorityCallHashedAddress()
{
return String.format("%02X", getMessage().getInt(PRIORITY_CALL_HASHED_ADDRESS)).toUpperCase();
}
/**
* Target radio address that is either a radio or talkgroup identifier
*/
public IntegerIdentifier getTargetAddress()
{
if(mTargetAddress == null)
{
if(isTalkgroupTargetAddress())
{
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
}
else
{
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
}
return mTargetAddress;
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceAddress()
{
if(mSourceAddress == null)
{
mSourceAddress = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mSourceAddress;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
mIdentifiers.add(getSourceAddress());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,276 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera XPT - Site State
*
* Note: I've only seen this documented in the patent and haven't yet seen it in the wild. So, I'm not sure if this
* is a CSBK or not. CSBK is the only thing that makes sense, but they've redefined bits 0 and 1 of the message which
* collides with CSBK bits 0 and 1.
*/
public class HyteraXPTSiteState extends CSBKMessage
{
private static final int[] SEQUENCE_NUMBER = new int[]{0, 1};
private static final int[] FREE_REPEATER = new int[]{16, 17, 18, 19};
private static final int[] REPEATER_A_STATE = new int[]{20, 21, 22, 23};
private static final int[] REPEATER_B_STATE = new int[]{24, 25, 26, 27};
private static final int[] REPEATER_C_STATE = new int[]{28, 29, 30, 31};
private static final int[] REPEATER_A_TS0_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] REPEATER_A_TS1_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] REPEATER_B_TS0_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] REPEATER_B_TS1_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] REPEATER_C_TS0_ADDRESS = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] REPEATER_C_TS1_ADDRESS = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public HyteraXPTSiteState(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Sequence number to indicate the set of site's repeaters that are being transmitted:
*
* SN 0: repeaters 1-3
* SN 1: repeaters 4-6
* SN 2: repeaters 7-9
*
* @return sequence number
*/
public int getSequenceNumber()
{
return getMessage().getInt(SEQUENCE_NUMBER);
}
/**
* Indicates if all channels of the site are busy.
*/
public boolean isAllChannelsBusy()
{
return getFreeRepeater() == 0;
}
/**
* Free repeater number to use when both timeslots of this repeater are busy
*/
public int getFreeRepeater()
{
return getMessage().getInt(FREE_REPEATER);
}
/**
* Repeater number for repeater A field for this message
*/
public int getRepeaterANumber()
{
switch(getSequenceNumber())
{
case 0:
return 1;
case 1:
return 4;
case 2:
return 7;
}
return 0;
}
/**
* Repeater number for repeater B field for this message
*/
public int getRepeaterBNumber()
{
switch(getSequenceNumber())
{
case 0:
return 2;
case 1:
return 5;
case 2:
return 8;
}
return 0;
}
/**
* Repeater number for repeater C field for this message
*/
public int getRepeaterCNumber()
{
switch(getSequenceNumber())
{
case 0:
return 3;
case 1:
return 6;
case 2:
return 9;
}
return 0;
}
/**
* State of Repeater A
*/
public int getRepeaterAStateInfo()
{
return getMessage().getInt(REPEATER_A_STATE);
}
/**
* State of Repeater B
*/
public int getRepeaterBStateInfo()
{
return getMessage().getInt(REPEATER_B_STATE);
}
/**
* State of Repeater C
*/
public int getRepeaterCStateInfo()
{
return getMessage().getInt(REPEATER_C_STATE);
}
/**
* Hashed Address that is currently using Repeater A Timeslot 0
*/
public String getRepeaterATimeslot0HashedAddress()
{
return String.format("%02X", getMessage().getInt(REPEATER_A_TS0_ADDRESS));
}
/**
* Hashed Address that is currently using Repeater A Timeslot 1
*/
public String getRepeaterATimeslot1HashedAddress()
{
return String.format("%02X", getMessage().getInt(REPEATER_A_TS1_ADDRESS));
}
/**
* Hashed Address that is currently using Repeater B Timeslot 0
*/
public String getRepeaterBTimeslot0HashedAddress()
{
return String.format("%02X", getMessage().getInt(REPEATER_B_TS0_ADDRESS));
}
/**
* Hashed Address that is currently using Repeater B Timeslot 1
*/
public String getRepeaterBTimeslot1HashedAddress()
{
return String.format("%02X", getMessage().getInt(REPEATER_B_TS1_ADDRESS));
}
/**
* Hashed Address that is currently using Repeater C Timeslot 0
*/
public String getRepeaterCTimeslot0HashedAddress()
{
return String.format("%02X", getMessage().getInt(REPEATER_C_TS0_ADDRESS));
}
/**
* Hashed Address that is currently using Repeater C Timeslot 1
*/
public String getRepeaterCTimeslot1HashedAddress()
{
return String.format("%02X", getMessage().getInt(REPEATER_C_TS1_ADDRESS));
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA XPT SITE ");
if(isAllChannelsBusy())
{
sb.append(" ALL CHANNELS BUSY");
}
else
{
sb.append(" FREE:").append(getFreeRepeater());
}
//Repeater A
sb.append(" STATE R").append(getRepeaterANumber()).append(":").append(getRepeaterAStateInfo());
sb.append(" (").append(getRepeaterATimeslot0HashedAddress()).append("/");
sb.append(getRepeaterATimeslot1HashedAddress()).append(")");
//Repeater B
sb.append(" R").append(getRepeaterBNumber()).append(":").append(getRepeaterBStateInfo());
sb.append(" (").append(getRepeaterBTimeslot0HashedAddress()).append("/");
sb.append(getRepeaterBTimeslot1HashedAddress()).append(")");
//Repeater C
sb.append(" R").append(getRepeaterCNumber()).append(":").append(getRepeaterCStateInfo());
sb.append(" (").append(getRepeaterCTimeslot0HashedAddress()).append("/");
sb.append(getRepeaterCTimeslot1HashedAddress()).append(")");
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,196 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
import java.util.ArrayList;
import java.util.List;
/**
* Capacity Plus - Aloha Message
*/
public class CapacityMaxAloha extends CSBKMessage
{
private static final int[] RESERVED = new int[]{16, 17};
private static final int SITE_TIMESLOT_SYNCHRONIZATION = 18;
private static final int[] VERSION = new int[]{19, 20, 21};
private static final int TIMING_OFFSET = 22;
private static final int ACTIVE_NETWORK_CONNECTION_FLAG = 23;
private static final int[] MASK = new int[]{24, 25, 26, 27, 28};
private static final int[] SERVICE_FUNCTION = new int[]{29, 30};
private static final int[] N_RAND_WAIT = new int[]{31, 32, 33, 34};
private static final int REGISTRATION_REQUIRED_FLAG = 35;
private static final int[] BACKOFF = new int[]{36, 37, 38, 39};
private static final int SYSTEM_IDENTITY_CODE_OFFSET = 40;
private static final int[] RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private SystemIdentityCode mSystemIdentityCode;
private RadioIdentifier mRadioIdentifier;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CapacityMaxAloha(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CAPACITY-MAX ALOHA");
if(hasRadioIdentifier())
{
sb.append(" TO:").append(getRadioIdentifier());
}
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
if(hasActiveNetworkConnection())
{
sb.append(" NET-CONNECTED");
}
else
{
sb.append(" NET-DISCONNECTED");
}
sb.append(" SERVICES:").append(getServiceFunction());
sb.append(" ETSI VER:").append(getVersion());
sb.append(" MASK:").append(getMask());
if(getSystemIdentityCode().getPAR().isMultipleControlChannels())
{
sb.append(" ").append(getSystemIdentityCode().getPAR());
}
sb.append(" ").append(getMessage().toHexString());
return sb.toString();
}
/**
* Services provided by the control channel.
*/
public ServiceFunction getServiceFunction()
{
return ServiceFunction.fromValue(getMessage().getInt(SERVICE_FUNCTION));
}
/**
* Mobile subscriber ID masking value. See: 102 361-4 p6.1.3
*/
public int getMask()
{
return getMessage().getInt(MASK);
}
public boolean hasActiveNetworkConnection()
{
return getMessage().get(ACTIVE_NETWORK_CONNECTION_FLAG);
}
/**
* DMR Tier III ETSI 102 361-4 ICD Version Number supported by this system
*/
public Version getVersion()
{
return Version.fromValue(getMessage().getInt(VERSION));
}
/**
* Acknowledged radio identifier
*/
public RadioIdentifier getRadioIdentifier()
{
if(mRadioIdentifier == null)
{
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
}
return mRadioIdentifier;
}
public boolean hasRadioIdentifier()
{
return getMessage().getInt(RADIO) != 0;
}
/**
* System Identity Code structure
*/
public SystemIdentityCode getSystemIdentityCode()
{
if(mSystemIdentityCode == null)
{
mSystemIdentityCode = new SystemIdentityCode(getMessage(), SYSTEM_IDENTITY_CODE_OFFSET, true);
}
return mSystemIdentityCode;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasRadioIdentifier())
{
mIdentifiers.add(getRadioIdentifier());
}
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,74 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.Collections;
import java.util.List;
/**
* Capacity Plus - CSBK 60 Message
*/
public class CapacityPlusCSBKO_60 extends CSBKMessage
{
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CapacityPlusCSBKO_60(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CAP+ *UNKNOWN* CSBK:60");
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,162 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Capacity+ Window Announcement for Enhanced GPS/Data Revert Channel
*
* The window size dictates how many Windows happen within each data super frame. You can infer the window size
* by monitoring the maximum observed window value before rollover to zero as follows:
*
* Window Size: Number of Windows (hex) for 30-Second Data Super Frames
* 5: 100 (0x52)
* 6: 83 (0x53)
* 7: 71 (0x47)
* 8: 62 (0x3E)
* 9: 55 (0x37)
* 10: 50 (0x32)
*
* Window Size: Number of Windows (hex) for 2-Minute Data Super Frames
* 1: 125 (0x7D)
* 2: 62 (0x3E)
*
* Field UNKNOWN_2 may be reserved for a radio identifier that the target radio should transmit to.
*/
public class CapacityPlusDataRevertWindowAnnouncement extends CSBKMessage
{
private static final int[] RESERVED = new int[]{16, 17, 18, 19, 20, 21, 22, 23,}; //Probably reserved for 32-bit addressing
private static final int[] TARGET_RADIO = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] WINDOW = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] SUPER_FRAME = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] UNKNOWN_1 = new int[]{56, 57, 58, 59, 60, 61, 62, 63}; //Always 0xFC
private static final int[] UNKNOWN_2 = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}; //Always 0x0000
private RadioIdentifier mTargetRadio;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CapacityPlusDataRevertWindowAnnouncement(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MOTOROLA CAP+ ENHANCED DATA REVERT ANNOUNCEMENT");
sb.append(" WINDOW:").append(getSuperFrame()).append(".").append(getWindow());
if(hasTargetRadio())
{
sb.append(" RESERVED FOR RADIO:").append(getTargetRadio());
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Indicates if there is a target radio specified in this message
*/
public boolean hasTargetRadio()
{
return getMessage().getInt(TARGET_RADIO) > 0;
}
/**
* Optional target radio identifier that the announced window is reserved for
* @return radio identifier or NULL
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null && hasTargetRadio())
{
mTargetRadio = DMRRadio.createTo(getMessage().getInt(TARGET_RADIO));
}
return mTargetRadio;
}
/**
* Current Super Frame Number
*
* @return 0-15
*/
public int getSuperFrame()
{
return getMessage().getInt(SUPER_FRAME);
}
/**
* Current Window Number
*
* @return current window number, values range from 0 to 125 depending on repeater configuration
*/
public int getWindow()
{
return getMessage().getInt(WINDOW);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasTargetRadio())
{
mIdentifiers.add(getTargetRadio());
}
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,119 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity+ Enhanced Data Revert Channel - Window Grant
*
* This appears to be a grant to an individual user to transmit data in a specified super frame and window.
*/
public class CapacityPlusDataRevertWindowGrant extends CSBKMessage
{
//Note: bits 16-23 may be reserved for 24-bit target address, however Cap+ radio IDs are limited to 16-bit values.
private static final int[] TARGET_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] WINDOW = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] SUPER_FRAME = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private RadioIdentifier mTargetAddress;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CapacityPlusDataRevertWindowGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MOTOROLA CAP+ ENHANCED DATA REVERT GRANT TO:").append(getTargetAddress());
sb.append(" TRANSMIT IN WINDOW:").append(getSuperFrame()).append(".").append(getWindow());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public RadioIdentifier getTargetAddress()
{
if(mTargetAddress == null)
{
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
return mTargetAddress;
}
/**
* Super frame (window) in which the target mobile should transmit their data
*/
public int getSuperFrame()
{
return getMessage().getInt(SUPER_FRAME);
}
/**
* Data frame window within the super frame in which the target mobile should transmit their data
*/
public int getWindow()
{
return getMessage().getInt(WINDOW);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,342 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus - Neighbor Sites
*/
public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] LC_START_STOP = new int[]{16, 17};
private static final int TIMESLOT = 18;
private static final int[] REST_REPEATER = new int[]{19, 20, 21, 22};
private static final int[] REST_TIMESLOT = new int[]{23};
private static final int ASYNC = 24;
private static final int[] SITE = new int[]{25, 26, 27, 28};
private static final int[] NEIGHBOR_COUNT = new int[]{29, 30, 31};
private static final int[] NEIGHBOR_1_SITE = new int[]{32, 33, 34, 35};
private static final int[] NEIGHBOR_1_REST = new int[]{36, 37, 38, 39};
private static final int[] NEIGHBOR_2_SITE = new int[]{40, 41, 42, 43};
private static final int[] NEIGHBOR_2_REST = new int[]{44, 45, 46, 47};
private static final int[] NEIGHBOR_3_SITE = new int[]{48, 49, 50, 51};
private static final int[] NEIGHBOR_3_REST = new int[]{52, 53, 54, 55};
private static final int[] NEIGHBOR_4_SITE = new int[]{56, 57, 58, 59};
private static final int[] NEIGHBOR_4_REST = new int[]{60, 61, 62, 63};
private static final int[] NEIGHBOR_5_SITE = new int[]{64, 65, 66, 67};
private static final int[] NEIGHBOR_5_REST = new int[]{68, 69, 70, 71};
private static final int[] NEIGHBOR_6_SITE = new int[]{72, 73, 74, 75};
private static final int[] NEIGHBOR_6_REST = new int[]{76, 77, 78, 79};
private DMRLogicalChannel mRestChannel;
private DMRSite mSite;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CapacityPlusNeighbors(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MOTOROLA CAP+ SITE:").append(getSite());
sb.append(" REST ").append(getRestChannel());
sb.append(" FL:").append(getLCSS());
if(hasNeighbor(1))
{
sb.append(" NEIGHBORS SITE:").append(getNeighbor1Site());
sb.append("-REST:").append(getNeighbor1Rest());
if(hasNeighbor(2))
{
sb.append(" SITE:").append(getNeighbor2Site());
sb.append("-REST:").append(getNeighbor2Rest());
if(hasNeighbor(3))
{
sb.append(" SITE:").append(getNeighbor3Site());
sb.append("-REST:").append(getNeighbor3Rest());
if(hasNeighbor(4))
{
sb.append(" SITE:").append(getNeighbor4Site());
sb.append("-REST:").append(getNeighbor4Rest());
if(hasNeighbor(5))
{
sb.append(" SITE:").append(getNeighbor5Site());
sb.append("-REST:").append(getNeighbor5Rest());
if(hasNeighbor(6))
{
sb.append(" SITE:").append(getNeighbor6Site());
sb.append("-REST:").append(getNeighbor6Rest());
}
}
}
}
}
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public boolean hasNeighbor(int neighbor)
{
return neighbor <= getNeighborCount();
}
/**
* Number of neighbors reported
*/
public int getNeighborCount()
{
return getMessage().getInt(NEIGHBOR_COUNT);
}
/**
* Neighbor 1 Site
*/
public int getNeighbor1Site()
{
return getMessage().getInt(NEIGHBOR_1_SITE);
}
/**
* Neighbor 1 Current Rest Channel
*/
public int getNeighbor1Rest()
{
return getMessage().getInt(NEIGHBOR_1_REST);
}
/**
* Neighbor 2 Site
*/
public int getNeighbor2Site()
{
return getMessage().getInt(NEIGHBOR_2_SITE);
}
/**
* Neighbor 2 Current Rest Channel
*/
public int getNeighbor2Rest()
{
return getMessage().getInt(NEIGHBOR_2_REST);
}
/**
* Neighbor 3 Site
*/
public int getNeighbor3Site()
{
return getMessage().getInt(NEIGHBOR_3_SITE);
}
/**
* Neighbor 3 Current Rest Channel
*/
public int getNeighbor3Rest()
{
return getMessage().getInt(NEIGHBOR_3_REST);
}
/**
* Neighbor 4 Site
*/
public int getNeighbor4Site()
{
return getMessage().getInt(NEIGHBOR_4_SITE);
}
/**
* Neighbor 4 Current Rest Channel
*/
public int getNeighbor4Rest()
{
return getMessage().getInt(NEIGHBOR_4_REST);
}
/**
* Neighbor 5 Site
*/
public int getNeighbor5Site()
{
return getMessage().getInt(NEIGHBOR_5_SITE);
}
/**
* Neighbor 5 Current Rest Channel
*/
public int getNeighbor5Rest()
{
return getMessage().getInt(NEIGHBOR_5_REST);
}
/**
* Neighbor 6 Site
*/
public int getNeighbor6Site()
{
return getMessage().getInt(NEIGHBOR_6_SITE);
}
/**
* Neighbor 6 Current Rest Channel
*/
public int getNeighbor6Rest()
{
return getMessage().getInt(NEIGHBOR_6_REST);
}
/**
* This site number
*
* @return
*/
public DMRSite getSite()
{
if(mSite == null)
{
mSite = DMRSite.create(getMessage().getInt(SITE));
}
return mSite;
}
/**
* Indicates if the site uses period beacons or asynchronous beacons on the rest channel.
*/
public String getBeacon()
{
return getMessage().get(ASYNC) ? "ASYNC" : "PERIODIC";
}
/**
* Message Fragment indicator
*/
public LCSS getLCSS()
{
return LCSS.fromValue(getMessage().getInt(LC_START_STOP));
}
/**
* Rest repeater
*/
public int getRestRepeater()
{
return getMessage().getInt(REST_REPEATER) + 1;
}
/**
* Rest timeslot
* @return timeslot 1 or 2
*/
public int getRestTimeslot()
{
return getMessage().getInt(REST_TIMESLOT) + 1;
}
/**
* DMR Channel
*/
public DMRLogicalChannel getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
}
return mRestChannel;
}
@Override
public int[] getLogicalTimeslotNumbers()
{
return getRestChannel().getLSNArray();
}
/**
* Assigns a timeslot frequency map for the DMR channel
*
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getRestChannel().getLogicalSlotNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
}
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getRestChannel());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,159 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus - Preamble
*/
public class CapacityPlusPreamble extends Preamble
{
private static final int RADIO_TALKGROUP_FLAG = 17;
private static final int[] BLOCKS_TO_FOLLOW = new int[]{18, 19, 20, 21, 22};
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] UNKNOWN_2 = new int[]{32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] TARGET_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] UNKNOWN_3 = new int[]{56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] SOURCE_ADDRESS = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private IntegerIdentifier mTargetAddress;
private RadioIdentifier mSourceAddress;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CapacityPlusPreamble(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MOTOROLA CAP+").append(isCSBKPreamble() ? " CSBK" : " DATA");
sb.append(" PREAMBLE FM:").append(getSourceAddress());
sb.append(" TO:").append(getTargetAddress());
sb.append(" BLOCKS TO FOLLOW:").append(getBlocksToFollow());
sb.append(" UNK1:").append(getUnknown1());
sb.append(" UNK2:").append(getUnknown2());
sb.append(" UNK3:").append(getUnknown3());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public String getUnknown1()
{
return getMessage().getHex(UNKNOWN_1, 2);
}
public String getUnknown2()
{
return getMessage().getHex(UNKNOWN_2, 2);
}
public String getUnknown3()
{
return getMessage().getHex(UNKNOWN_3, 2);
}
/**
* Number of data blocks that will follow this preamble
*/
public int getBlocksToFollow()
{
return getMessage().getInt(BLOCKS_TO_FOLLOW);
}
/**
* Target radio address that is either a radio or talkgroup identifier
*/
public IntegerIdentifier getTargetAddress()
{
if(mTargetAddress == null)
{
if(getMessage().get(RADIO_TALKGROUP_FLAG))
{
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
}
else
{
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
}
return mTargetAddress;
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceAddress()
{
if(mSourceAddress == null)
{
mSourceAddress = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mSourceAddress;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
mIdentifiers.add(getSourceAddress());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,158 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
import java.util.ArrayList;
import java.util.List;
/**
* Capacity+ System Status CSBKO=62 Message
*/
public class CapacityPlusSystemStatus extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] FRAGMENT_INDICATOR = new int[]{16, 17};
// private static final int TIMESLOT = 18;
private static final int[] REST_REPEATER = new int[]{19, 20, 21, 22};
private static final int[] REST_TIMESLOT = new int[]{23};
private DMRLogicalChannel mRestChannel;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CapacityPlusSystemStatus(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MOTOROLA CAP+ SYSTEM STATUS ").append(getFragmentIndicator());
sb.append(" REST ").append(getRestChannel());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Fragment indicator for system status message values that are fragmented across multiple system status
* messages.
*/
public LCSS getFragmentIndicator()
{
return LCSS.fromValue(getMessage().getInt(FRAGMENT_INDICATOR));
}
/**
* Current rest channel for this site.
*/
public DMRLogicalChannel getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
}
return mRestChannel;
}
/**
* Rest Channel Repeater
*/
public int getRestRepeater()
{
return getMessage().getInt(REST_REPEATER) + 1;
}
/**
* Rest Channel Timeslot
* @return 1 or 2
*/
public int getRestTimeslot()
{
return getMessage().getInt(REST_TIMESLOT) + 1;
}
/**
* Logical slot numbers that require slot to frequency mappings.
*/
@Override
public int[] getLogicalTimeslotNumbers()
{
return getRestChannel().getLSNArray();
}
/**
* Applies logical slot number to frequency mapping.
*
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
}
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getRestChannel());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,129 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Connect Plus - CSBK 16 Message
*/
public class ConnectPlusCSBKO_16 extends CSBKMessage
{
private static final int[] SOURCE_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] TARGET_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] UNKNOWN = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
private RadioIdentifier mSourceRadio;
private RadioIdentifier mTargetRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusCSBKO_16(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ CSBKO:16 FM:").append(getSourceRadio());
sb.append(" TO:").append(getTargetRadio());
sb.append(" UNK:").append(getUnknown());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Unknown Field
*/
public String getUnknown()
{
return String.format("%04X", getMessage().getInt(UNKNOWN));
}
/**
* Source Radio ID
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mSourceRadio;
}
/**
* Target Radio ID
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null)
{
mTargetRadio = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
return mTargetRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSourceRadio());
mIdentifiers.add(getTargetRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,173 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Connect Plus - Data Channel Grant
*/
public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] TARGET_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] REPEATER = new int[]{40, 41, 42, 43};
private static final int[] CHANNEL_GRANT_TIMESLOT = new int[]{44};
//Analysis: this field correlates to UNKNOWN_FIELD_1(bits: 40-48) in ConnectPlusTerminateChannelGrant.
private static final int[] UNKNOWN_FIELD = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private RadioIdentifier mTargetRadio;
private DMRLogicalChannel mDMRLogicalChannel;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusDataChannelGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CSBK ").append(getVendor());
sb.append(" DATA CHANNEL GRANT TO:").append(getTargetRadio());
sb.append(" ").append(getChannel());
sb.append(" UNK:").append(getUnknownField());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Target radio address
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null)
{
mTargetRadio = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
return mTargetRadio;
}
/**
* Unknown field
*/
public int getUnknownField()
{
return getMessage().getInt(UNKNOWN_FIELD);
}
/**
* Channel grant repeater number
*/
public int getRepeater()
{
return getMessage().getInt(REPEATER);
}
/**
* Channel grant timeslot
* @return 1 or 2
*/
public int getChannelGrantTimeslot()
{
return getMessage().getInt(CHANNEL_GRANT_TIMESLOT) + 1;
}
/**
* DMR Channel
*/
public DMRLogicalChannel getChannel()
{
if(mDMRLogicalChannel == null)
{
mDMRLogicalChannel = new DMRLogicalChannel(getRepeater(), getChannelGrantTimeslot());
}
return mDMRLogicalChannel;
}
@Override
public int[] getLogicalTimeslotNumbers()
{
return getChannel().getLSNArray();
}
/**
* Assigns a timeslot frequency map for the DMR channel
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
{
getChannel().setTimeslotFrequency(timeslotFrequency);
}
}
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetRadio());
mIdentifiers.add(getChannel());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,137 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Connect Plus - Fast GPS or Enhanced GPS / Data Revert Window Announcement.
*/
public class ConnectPlusDataRevertWindowAnnouncement extends CSBKMessage
{
private static final int[] WINDOW = new int[]{16, 17, 18, 19, 20, 21, 22};
private static final int[] SUPERFRAME = new int[]{24, 25, 26, 27};
private static final int[] REPEATER = new int[]{28, 29, 30, 31}; //This may be the repeater number
private static final int[] TARGET_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55};
private List<Identifier> mIdentifiers;
private RadioIdentifier mTargetRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusDataRevertWindowAnnouncement(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ ENHANCED DATA REVERT CHANNEL ANNOUNCE WINDOW:");
sb.append(getSuperFrame()).append(".").append(getWindow());
if(hasTargetRadio())
{
sb.append(" RESERVED FOR:").append(getTargetRadio());
}
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Radio identifier that this window is reserved for.
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null)
{
mTargetRadio = DMRRadio.createTo(getMessage().getInt(TARGET_RADIO));
}
return mTargetRadio;
}
/**
* Indicates if this GPS transmit window is reserved for a specific radio identifier.
*/
public boolean hasTargetRadio()
{
return getMessage().getInt(TARGET_RADIO) > 0;
}
/**
* GPS Window Super Frame
*/
public int getSuperFrame()
{
return getMessage().getInt(SUPERFRAME);
}
/**
* GPS Window within a super frame
*/
public int getWindow()
{
return getMessage().getInt(WINDOW);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasTargetRadio())
{
mIdentifiers.add(getTargetRadio());
}
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,122 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Connect Plus - Enhanced Data Revert - Window Grant
*/
public class ConnectPlusDataRevertWindowGrant extends CSBKMessage
{
private static final int[] TARGET_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] SUPERFRAME = new int[]{48, 49, 50, 51};
//TODO: this may not be correct
private static final int[] WINDOW = new int[]{70, 71, 72, 73, 74};
private List<Identifier> mIdentifiers;
private RadioIdentifier mTargetRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusDataRevertWindowGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ ENHANCED DATA REVERT GRANT TO:").append(getTargetRadio());
sb.append(" TRANSMIT IN WINDOW:").append(getSuperFrame()).append(".").append(getWindow());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* GPS Window Super Frame
*/
public int getSuperFrame()
{
return getMessage().getInt(SUPERFRAME);
}
/**
* GPS Window within a super frame
*/
public int getWindow()
{
return getMessage().getInt(WINDOW);
}
/**
* Current radio user for the channel
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null)
{
mTargetRadio = DMRRadio.createFrom(getMessage().getInt(TARGET_ADDRESS));
}
return mTargetRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,143 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import com.google.common.base.Joiner;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.site.SiteIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Connect Plus - Neighbor Report
*/
public class ConnectPlusNeighborReport extends CSBKMessage
{
private static final int SITE_ARRAY_START = 16;
private static final int[] UNKNOWN = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private List<SiteIdentifier> mNeighbors;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusNeighborReport(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ NEIGHBORS:");
List<SiteIdentifier> neighbors = getNeighbors();
if(neighbors.isEmpty())
{
sb.append("NONE");
}
else
{
sb.append(Joiner.on(",").join(getNeighbors()));
}
sb.append(" UNK:").append(getUnknownField());
// sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public String getUnknownField()
{
return getMessage().getHex(UNKNOWN, 6);
}
/**
* List of neighbor site identifiers
*/
public List<SiteIdentifier> getNeighbors()
{
if(mNeighbors == null)
{
mNeighbors = new ArrayList<>();
for(int x = 0; x <= 5; x++)
{
int site = getSite(x);
if(site > 0)
{
mNeighbors.add(DMRSite.create(site));
}
}
}
return mNeighbors;
}
/**
* Retrieves the specified neighbor site value.
*
* @param index 0 - 6
* @return site id value. A value of zero indicates the field is empty.
*/
private int getSite(int index)
{
int start = SITE_ARRAY_START + (index * 8);
int end = start + 7;
return getMessage().getInt(start, end);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.addAll(getNeighbors());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,163 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.ip.xcmp.XCMPMessageType;
import java.util.ArrayList;
import java.util.List;
/**
* Connect Plus - Over The Air (OTA) Reprogramming Announcement
*
* Note: there are at least 2 file types that can be transferred:
* 1. Network Frequency File
* 2. Option Board Firmware
*/
public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] MESSAGE_TYPE = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] VERSION = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] UNKNOWN = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63};
private static final int[] DATA_REPEATER = new int[]{64, 65, 66, 67};
private static final int[] DATA_TIMESLOT = new int[]{68};
private DMRLogicalChannel mDataChannel;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusOTAAnnouncement(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ ANNOUNCE OTA ").append(getMessageType());
sb.append(" VER:").append(getMessageVersion());
sb.append(" AVAILABLE ON ").append(getDataChannel());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public XCMPMessageType getMessageType()
{
return XCMPMessageType.fromValue(getMessage().getInt(MESSAGE_TYPE));
}
public int getMessageVersion()
{
return getMessage().getInt(VERSION);
}
/**
* Data repeater number
*/
public int getDataRepeater()
{
return getMessage().getInt(DATA_REPEATER);
}
/**
* Data Timeslot
* @return 1 or 2
*/
public int getDataTimeslot()
{
return getMessage().getInt(DATA_TIMESLOT) + 1;
}
/**
* DMR Channel where the data is available
*/
public DMRLogicalChannel getDataChannel()
{
if(mDataChannel == null)
{
mDataChannel = new DMRLogicalChannel(getDataRepeater(), getDataTimeslot());
}
return mDataChannel;
}
@Override
public int[] getLogicalTimeslotNumbers()
{
return getDataChannel().getLSNArray();
}
/**
* Assigns a timeslot frequency map for the DMR channel
*
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getDataChannel().getLogicalSlotNumber())
{
getDataChannel().setTimeslotFrequency(timeslotFrequency);
}
}
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getDataChannel());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,129 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Connect Plus - Registration Request
*/
public class ConnectPlusRegistrationRequest extends CSBKMessage
{
private static final int[] SOURCE_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] TARGET_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] UNKNOWN = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
private List<Identifier> mIdentifiers;
private RadioIdentifier mSourceRadio;
private RadioIdentifier mTargetRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusRegistrationRequest(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ REGISTRATION REQUEST FM:").append(getSourceRadio());
sb.append(" TO:").append(getTargetRadio());
sb.append(" UNK:").append(getUnknown());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Unknown
*/
public int getUnknown()
{
return getMessage().getInt(UNKNOWN);
}
/**
* Source Radio ID
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mSourceRadio;
}
/**
* Target Radio ID
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null)
{
mTargetRadio = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
return mTargetRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSourceRadio());
mIdentifiers.add(getTargetRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,129 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Connect Plus - Registration Response
*/
public class ConnectPlusRegistrationResponse extends CSBKMessage
{
private static final int[] SOURCE_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] TARGET_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] UNKNOWN = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
private List<Identifier> mIdentifiers;
private RadioIdentifier mSourceRadio;
private RadioIdentifier mTargetRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusRegistrationResponse(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ REGISTRATION RESPONSE FM:").append(getSourceRadio());
sb.append(" TO:").append(getTargetRadio());
sb.append(" UNK:").append(getUnknown());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Unknown
*/
public int getUnknown()
{
return getMessage().getInt(UNKNOWN);
}
/**
* Source Radio ID
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mSourceRadio;
}
/**
* Target Radio ID
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null)
{
mTargetRadio = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
return mTargetRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSourceRadio());
mIdentifiers.add(getTargetRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,131 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Connect Plus - Radio:Talkgroup Affiliation
*/
public class ConnectPlusTalkgroupAffiliation extends CSBKMessage
{
private static final int[] RADIO = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] TALKGROUP = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] UNKNOWN = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
private List<Identifier> mIdentifiers;
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusTalkgroupAffiliation(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ AFFILIATE RADIO:").append(getRadio());
sb.append(" TO TALKGROUP:").append(getTalkgroup());
sb.append(" UNK:").append(getUnknown());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Unknown
*/
public int getUnknown()
{
return getMessage().getInt(UNKNOWN);
}
/**
* Radio ID that is being affiliated
*/
public RadioIdentifier getRadio()
{
if(mRadio == null)
{
mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO));
}
return mRadio;
}
/**
* Talkgroup that the radio is affiliated to
*/
public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
}
return mTalkgroup;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getRadio());
mIdentifiers.add(getTalkgroup());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,155 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Connect Plus - Terminate Channel Grant
*/
public class ConnectPlusTerminateChannelGrant extends CSBKMessage
{
private static final int[] TARGET_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39};
//Analysis: this field correlates to UNKNOWN_FIELD(bits: 48-55) in ConnectPlusDataChannelGrant.
private static final int[] UNKNOWN_FIELD_1 = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] UNKNOWN_FIELD_2 = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] UNKNOWN_FIELD_3 = new int[]{56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] UNKNOWN_FIELD_4 = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] UNKNOWN_FIELD_5 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mTargetRadio;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusTerminateChannelGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CSBK ").append(getVendor());
sb.append(" TERMINATE CHANNEL GRANT TO:").append(getTargetRadio());
sb.append(" U1:").append(getUnknownField1());
sb.append(" U2:").append(getUnknownField2());
sb.append(" U3:").append(getUnknownField3());
sb.append(" U4:").append(getUnknownField4());
sb.append(" U5:").append(getUnknownField5());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Target radio address
*/
public RadioIdentifier getTargetRadio()
{
if(mTargetRadio == null)
{
mTargetRadio = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
return mTargetRadio;
}
/**
* Unknown field
*/
public int getUnknownField1()
{
return getMessage().getInt(UNKNOWN_FIELD_1);
}
/**
* Unknown field
*/
public int getUnknownField2()
{
return getMessage().getInt(UNKNOWN_FIELD_2);
}
/**
* Unknown field
*/
public int getUnknownField3()
{
return getMessage().getInt(UNKNOWN_FIELD_3);
}
/**
* Unknown field
*/
public int getUnknownField4()
{
return getMessage().getInt(UNKNOWN_FIELD_4);
}
/**
* Unknown field
*/
public int getUnknownField5()
{
return getMessage().getInt(UNKNOWN_FIELD_5);
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,189 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Connect Plus - Voice Channel User
*/
public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] SOURCE_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] TRAFFIC_CHANNEL_REPEATER = new int[]{64, 65, 66, 67};
private static final int[] TRAFFIC_CHANNEL_TIMESLOT = new int[]{68};
private static final int[] UNKNOWN_FIELD = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
private DMRLogicalChannel mDMRLogicalChannel;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ConnectPlusVoiceChannelUser(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CON+ VOICE CHANNEL USER FM:").append(getRadio());
sb.append(" TO:").append(getTalkgroup());
sb.append(" ").append(getChannel());
sb.append(" UNK:").append(getUnknownField());
return sb.toString();
}
/**
* Source radio address
*/
public RadioIdentifier getRadio()
{
if(mRadio == null)
{
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mRadio;
}
/**
* Talkgroup address
*/
public TalkgroupIdentifier getTalkgroup()
{
if(mTalkgroup == null)
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
}
return mTalkgroup;
}
/**
* Unknown field
*/
public int getUnknownField()
{
return getMessage().getInt(UNKNOWN_FIELD);
}
/**
* Traffic channel repeater
*/
public int getTrafficChannelRepeater()
{
return getMessage().getInt(TRAFFIC_CHANNEL_REPEATER);
}
/**
* Traffic channel timeslot
* @return 1 or 2
*/
public int getTrafficChannelTimeslot()
{
return getMessage().getInt(TRAFFIC_CHANNEL_TIMESLOT) + 1;
}
/**
* DMR Channel
*/
public DMRLogicalChannel getChannel()
{
if(mDMRLogicalChannel == null)
{
mDMRLogicalChannel = new DMRLogicalChannel(getTrafficChannelRepeater(), getTrafficChannelTimeslot());
}
return mDMRLogicalChannel;
}
@Override
public int[] getLogicalTimeslotNumbers()
{
return getChannel().getLSNArray();
}
/**
* Assigns a timeslot frequency map for the DMR channel
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
{
getChannel().setTimeslotFrequency(timeslotFrequency);
}
}
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTalkgroup());
mIdentifiers.add(getRadio());
mIdentifiers.add(getChannel());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,196 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Aloha Message
*/
public class Aloha extends CSBKMessage
{
private static final int[] RESERVED = new int[]{16, 17};
private static final int SITE_TIMESLOT_SYNCHRONIZATION = 18;
private static final int[] VERSION = new int[]{19, 20, 21};
private static final int TIMING_OFFSET = 22;
private static final int ACTIVE_NETWORK_CONNECTION_FLAG = 23;
private static final int[] MASK = new int[]{24, 25, 26, 27, 28};
private static final int[] SERVICE_FUNCTION = new int[]{29, 30};
private static final int[] N_RAND_WAIT = new int[]{31, 32, 33, 34};
private static final int REGISTRATION_REQUIRED_FLAG = 35;
private static final int[] BACKOFF = new int[]{36, 37, 38, 39};
private static final int SYSTEM_IDENTITY_CODE_OFFSET = 40;
private static final int[] RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private SystemIdentityCode mSystemIdentityCode;
private RadioIdentifier mRadioIdentifier;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public Aloha(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" ALOHA");
if(hasRadioIdentifier())
{
sb.append(" TO:").append(getRadioIdentifier());
}
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
if(hasActiveNetworkConnection())
{
sb.append(" NET-CONNECTED");
}
else
{
sb.append(" NET-DISCONNECTED");
}
sb.append(" SERVICES:").append(getServiceFunction());
sb.append(" ETSI VER:").append(getVersion());
sb.append(" MASK:").append(getMask());
if(getSystemIdentityCode().getPAR().isMultipleControlChannels())
{
sb.append(" ").append(getSystemIdentityCode().getPAR());
}
sb.append(" ").append(getMessage().toHexString());
return sb.toString();
}
/**
* Services provided by the control channel.
*/
public ServiceFunction getServiceFunction()
{
return ServiceFunction.fromValue(getMessage().getInt(SERVICE_FUNCTION));
}
/**
* Mobile subscriber ID masking value. See: 102 361-4 p6.1.3
*/
public int getMask()
{
return getMessage().getInt(MASK);
}
public boolean hasActiveNetworkConnection()
{
return getMessage().get(ACTIVE_NETWORK_CONNECTION_FLAG);
}
/**
* DMR Tier III ETSI 102 361-4 ICD Version Number supported by this system
*/
public Version getVersion()
{
return Version.fromValue(getMessage().getInt(VERSION));
}
/**
* Acknowledged radio identifier
*/
public RadioIdentifier getRadioIdentifier()
{
if(mRadioIdentifier == null)
{
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
}
return mRadioIdentifier;
}
public boolean hasRadioIdentifier()
{
return getMessage().getInt(RADIO) != 0;
}
/**
* System Identity Code structure
*/
public SystemIdentityCode getSystemIdentityCode()
{
if(mSystemIdentityCode == null)
{
mSystemIdentityCode = new SystemIdentityCode(getMessage(), SYSTEM_IDENTITY_CODE_OFFSET, true);
}
return mSystemIdentityCode;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasRadioIdentifier())
{
mIdentifiers.add(getRadioIdentifier());
}
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,265 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Clear Channel (return to control channel)
*/
public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] CHANNEL_NUMBER = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
private static final int TALKGROUP_FLAG = 31;
protected static final int[] DESTINATION = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55};
protected static final int[] SOURCE = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private DMRChannel mChannel;
private AbsoluteChannelParameters mAbsoluteChannelParameters;
private RadioIdentifier mSourceRadio;
private IntegerIdentifier mDestinationId;
private List<Identifier> mIdentifiers;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public Clear(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Constructs a multi-block MBC instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
* @param multiBlock continuation containing absolute channel parameters
*/
public Clear(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
this(syncPattern, message, cach, slotType, timestamp, timeslot);
if(multiBlock != null)
{
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0,
getMoveToTimeslot());
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" CLEAR - RETURN TO ").append(getMoveToChannel());
sb.append(" FM:").append(getSourceRadio());
sb.append(" TO:").append(getDestinationId());
return sb.toString();
}
/**
* Checks CRC and sets the message valid flag according to the results.
*/
public void checkCRC()
{
if(getSlotType().getDataType() == DataType.MBC_HEADER)
{
checkMultiBlockCRC(getAbsoluteChannelParameters());
}
else
{
//Use the standard CSBK CRC check
super.checkCRC();
}
}
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
}
return mSourceRadio;
}
public IntegerIdentifier getDestinationId()
{
if(mDestinationId == null)
{
if(getMessage().get(TALKGROUP_FLAG))
{
mDestinationId = DMRTalkgroup.create(getMessage().getInt(DESTINATION));
}
else
{
mDestinationId = DMRRadio.createTo(getMessage().getInt(DESTINATION));
}
}
return mDestinationId;
}
/**
* Timeslot to use for the channel grant
*/
private int getMoveToTimeslot()
{
return 1; //Back to the control channel timeslot
}
/**
* Channel number to move to
*/
private int getMoveToChannelNumber()
{
return getMessage().getInt(CHANNEL_NUMBER);
}
/**
* Absolute channel parameters structure
*/
public AbsoluteChannelParameters getAbsoluteChannelParameters()
{
return mAbsoluteChannelParameters;
}
/**
* Indicates if this message has an absolute channel parameters structure
*/
public boolean hasAbsoluteChannelParameters()
{
return getAbsoluteChannelParameters() != null;
}
/**
* DMR Channel and Timeslot to move to
*/
public DMRChannel getMoveToChannel()
{
if(hasAbsoluteChannelParameters())
{
return mAbsoluteChannelParameters.getChannel();
}
if(mChannel == null)
{
mChannel = new DMRLogicalChannel(getMoveToChannelNumber(), getMoveToTimeslot());
}
return mChannel;
}
/**
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
{
if(getMoveToChannel() instanceof DMRLogicalChannel)
{
return ((DMRLogicalChannel)getMoveToChannel()).getLSNArray();
}
return new int[0];
}
/**
* Applies the timeslot frequency lookup information to channels contained in this message
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getMoveToChannel() instanceof DMRLogicalChannel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getMoveToChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
}
}
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getMoveToChannel());
mIdentifiers.add(getSourceRadio());
mIdentifiers.add(getDestinationId());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,207 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Move Trunking System Control Channel (TSCC)
*/
public class MoveTSCC extends CSBKMessage
{
private static final int[] RESERVED = new int[]{16, 17, 18, 19, 20, 20, 22, 23, 24};
private static final int[] MASK = new int[]{25, 26, 27, 28, 29};
private static final int[] RESERVED_2 = new int[]{30, 31, 32, 33, 34};
private static final int REGISTRATION_REQUIRED_FLAG = 35;
private static final int[] BACKOFF = new int[]{36, 37, 38, 39};
private static final int[] RESERVED_3 = new int[]{40, 41, 42, 43};
private static final int[] CHANNEL_NUMBER = new int[]{44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadioIdentifier;
private DMRChannel mDMRChannel;
private AbsoluteChannelParameters mAbsoluteChannelParameters;
private List<Identifier> mIdentifiers;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public MoveTSCC(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Constructs a multi-block MBC instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
* @param multiBlock containing absolute frequency parameters
*/
public MoveTSCC(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
if(multiBlock != null)
{
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
}
}
/**
* Checks CRC and sets the message valid flag according to the results.
*/
public void checkCRC()
{
if(getSlotType().getDataType() == DataType.MBC_HEADER)
{
checkMultiBlockCRC(getAbsoluteChannelParameters());
}
else
{
//Use the standard CSBK CRC check
super.checkCRC();
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MOVE TRUNK CONTROL CHANNEL ").append(getChannel());
if(hasRadioIdentifier())
{
sb.append(" TO:").append(getRadioIdentifier());
}
return sb.toString();
}
/**
* Absolute channel parameters structure
*/
public AbsoluteChannelParameters getAbsoluteChannelParameters()
{
return mAbsoluteChannelParameters;
}
/**
* Indicates if this message has an absolute channel parameters structure
*/
public boolean hasAbsoluteChannelParameters()
{
return getAbsoluteChannelParameters() != null;
}
public DMRChannel getChannel()
{
if(mDMRChannel == null)
{
if(hasAbsoluteChannelParameters())
{
mDMRChannel = getAbsoluteChannelParameters().getChannel();
}
else
{
mDMRChannel = new DMRLogicalChannel(getMessage().getInt(CHANNEL_NUMBER), 1);
}
}
return mDMRChannel;
}
/**
* Mobile subscriber ID masking value. See: 102 361-4 p6.1.3
*/
public int getMask()
{
return getMessage().getInt(MASK);
}
/**
* Acknowledged radio identifier
*/
public RadioIdentifier getRadioIdentifier()
{
if(mRadioIdentifier == null)
{
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
}
return mRadioIdentifier;
}
public boolean hasRadioIdentifier()
{
return getMessage().getInt(RADIO) != 0;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasRadioIdentifier())
{
mIdentifiers.add(getRadioIdentifier());
}
mIdentifiers.add(getChannel());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,161 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Preamble
*/
public class Preamble extends CSBKMessage
{
private static final int DATA_CSBK_FLAG = 16;
private static final int RADIO_TALKGROUP_FLAG = 17;
private static final int[] BLOCKS_TO_FOLLOW = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79};
private IntegerIdentifier mTargetAddress;
private RadioIdentifier mSourceAddress;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public Preamble(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CSBK PREAMBLE FM:").append(getSourceAddress());
sb.append(" TO:").append(getTargetAddress());
sb.append(isCSBKPreamble() ? " CSBK" : " DATA");
sb.append(" BLOCKS TO FOLLOW:").append(getBlocksToFollow());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
public boolean isTalkgroupTargetAddress()
{
return getMessage().get(RADIO_TALKGROUP_FLAG);
}
/**
* Indicates if this is a preamble for a data packet or sequence
*/
public boolean isDataPreamble()
{
return getMessage().get(DATA_CSBK_FLAG);
}
/**
* Indicates if this is a preamble for CSBK content
*/
public boolean isCSBKPreamble()
{
return !getMessage().get(DATA_CSBK_FLAG);
}
/**
* Number of data blocks that will follow this preamble
*/
public int getBlocksToFollow()
{
return getMessage().getInt(BLOCKS_TO_FOLLOW);
}
/**
* Target radio address that is either a radio or talkgroup identifier
*/
public IntegerIdentifier getTargetAddress()
{
if(mTargetAddress == null)
{
if(isTalkgroupTargetAddress())
{
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
}
else
{
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
}
return mTargetAddress;
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceAddress()
{
if(mSourceAddress == null)
{
mSourceAddress = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
}
return mSourceAddress;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
mIdentifiers.add(getSourceAddress());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,145 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ProtectKind;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Protect
*/
public class Protect extends CSBKMessage
{
private static final int[] PROTECT_KIND = new int[]{28, 29, 30};
private static final int TALKGROUP_FLAG = 31;
protected static final int[] DESTINATION = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55};
protected static final int[] SOURCE = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
private RadioIdentifier mSourceRadio;
private IntegerIdentifier mDestinationId;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public Protect(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" PROTECT: ").append(getProtectKind());
sb.append(" FM:").append(getSourceRadio());
sb.append(" TO:").append(getDestinationId());
return sb.toString();
}
/**
* Kind or type of protect command being issued.
*/
public ProtectKind getProtectKind()
{
return ProtectKind.fromValue(getMessage().getInt(PROTECT_KIND));
}
/**
* Source radio ID
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
}
return mSourceRadio;
}
/**
* Destination ID
*/
public IntegerIdentifier getDestinationId()
{
if(mDestinationId == null)
{
if(getMessage().get(TALKGROUP_FLAG))
{
mDestinationId = DMRTalkgroup.create(getMessage().getInt(DESTINATION));
}
else
{
mDestinationId = DMRRadio.createTo(getMessage().getInt(DESTINATION));
}
}
return mDestinationId;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSourceRadio());
mIdentifiers.add(getDestinationId());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,184 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.acknowledge;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Acknowledge Response
*/
public class Acknowledge extends CSBKMessage
{
private static final int TARGET_GROUP_INDIVIDUAL_FLAG = 16;
protected static final int[] RESPONSE_INFO = new int[]{16, 17, 18, 19, 20, 21, 22};
private static final int[] ACKNOWLEDGE_TYPE = new int[]{23, 24};
//Note: acknowledge type is the first 2 bits of the reason code - intentional overlap
private static final int[] REASON_CODE = new int[]{23, 24, 25, 26, 27, 28, 29, 30};
private static final int RESERVED = 31;
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] ADDITIONAL_INFO_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
protected List<Identifier> mIdentifiers;
private IntegerIdentifier mTargetAddress;
private RadioIdentifier mSourceRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public Acknowledge(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" ").append(getAcknowledgeType());
sb.append(" REASON:").append(getReason());
sb.append(" FM:").append(getSourceRadio());
sb.append(" TO:").append(getTargetAddress());
return sb.toString();
}
/**
* Indicates if the target address is a talkgroup (true) or radio/gateway (false).
*/
public boolean isTargetAddressTalkgroup()
{
return getMessage().get(TARGET_GROUP_INDIVIDUAL_FLAG);
}
/**
* Acknowledgement message type
*/
public AcknowledgeType getAcknowledgeType()
{
return AcknowledgeType.fromValue(getMessage().getInt(ACKNOWLEDGE_TYPE));
}
/**
* Reason code.
*
* Note: the reason entries are coupled with the acknowledgement type and can optionally indicate when the
* additional information field contains amplifying information.
*/
public Reason getReason()
{
return getReason(getMessage());
}
/**
* Utility method to determine the reason code for the specified message
* @param message containing a reason code field
* @return reason
*/
public static Reason getReason(CorrectedBinaryMessage message)
{
return Reason.fromValue(message.getInt(REASON_CODE));
}
public IntegerIdentifier getTargetAddress()
{
if(mTargetAddress == null)
{
if(isTargetAddressTalkgroup())
{
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
}
else
{
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
}
return mTargetAddress;
}
public boolean hasSourceRadio()
{
return true; //TODO: delineate valid reason codes here
}
/**
* Source radio identifier
*
* Note: the source radio field is dual-purpose and contains either a source radio address or
* additional information, depending on the reason code and/or acknowledgement type. Use the
* hasSourceRadio() method to first check if this message carries a source radio identifier.
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(ADDITIONAL_INFO_SOURCE_ADDRESS));
}
return mSourceRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
if(hasSourceRadio())
{
mIdentifiers.add(getSourceRadio());
}
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,90 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRUnitStatus;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import java.util.ArrayList;
import java.util.List;
/**
* Mobile Subscriber Status Acknowledged
*
* Indicates the controller accepts the status message and will forward to the status service.
*/
public class AcknowledgeStatus extends Acknowledge
{
private DMRUnitStatus mUnitStatus;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public AcknowledgeStatus(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Status value sent by the mobile subscriber
*/
public DMRUnitStatus getUnitStatus()
{
if(mUnitStatus == null)
{
mUnitStatus = DMRUnitStatus.create(getMessage().getInt(RESPONSE_INFO));
}
return mUnitStatus;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" ACKNOWLEDGE STATUS:").append(getUnitStatus());
sb.append(" TO:").append(getTargetAddress());
if(hasSourceRadio())
{
sb.append(" FM:").append(getSourceRadio());
}
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
if(hasSourceRadio())
{
mIdentifiers.add(getSourceRadio());
}
mIdentifiers.add(getUnitStatus());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,66 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
/**
* Registration Accepted
*/
public class RegistrationAccepted extends Acknowledge
{
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public RegistrationAccepted(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Power Save offset that the mobile subscriber should monitor when in power save mode.
* @return frame, 1-8
*/
public int getPowerSaveOffset()
{
return getMessage().getInt(RESPONSE_INFO);
}
/**
* Indicates if a power save offset value is specified by the TSCC
*/
public boolean hasPowerSaveOffset()
{
return getPowerSaveOffset() > 0;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" REGISTRATION ACCEPTED TO:").append(getTargetAddress());
sb.append(" FM:").append(getSourceRadio());
if(hasPowerSaveOffset())
{
sb.append(" POWER SAVE OFFSET:").append(getPowerSaveOffset());
}
return sb.toString();
}
}

View File

@@ -0,0 +1,135 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
/**
* DMR Tier III - Ahoy!
*/
public abstract class Ahoy extends CSBKMessage
{
private static final int[] SERVICE_OPTIONS_MIRROR = new int[]{16, 17, 18, 19, 20, 21, 22};
private static final int ENCRYPTED_SERVICE_OPTION_FLAG = 17;
protected static final int SERVICE_KIND_FLAG = 23;
private static final int AMBIENT_LISTENING_SERVICE_FLAG = 24;
private static final int TALKGROUP_FLAG = 25;
protected static final int[] APPENDED_BLOCKS_OR_STATUS = new int[]{26, 27};
private static final int[] SERVICE_KIND = new int[]{28, 29, 30, 31};
protected static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
47, 48, 49, 50, 51, 52, 53, 54, 55};
protected static final int[] MULTI_PURPOSE_FIELD = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private IntegerIdentifier mTargetAddress;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public Ahoy(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Utility method to lookup service kind for an AHOY message
*/
public static ServiceKind getServiceKind(CorrectedBinaryMessage message)
{
return ServiceKind.fromValue(message.getInt(SERVICE_KIND));
}
/**
* Value of the multi-purpose field.
*/
public int getMultiPurposeFieldValue()
{
return getMessage().getInt(MULTI_PURPOSE_FIELD);
}
/**
* Service Kind for this message
*/
public ServiceKind getServiceKind()
{
return getServiceKind(getMessage());
}
/**
* Flag that has meaning depending on the Service Kind value
*/
public boolean isServiceKindFlag()
{
return getMessage().get(SERVICE_KIND_FLAG);
}
/**
* Indicates if the calling party requests Ambient Listening Service (ALS).
* @return
*/
public boolean isAmbientListeningServiceRequest()
{
return getMessage().get(AMBIENT_LISTENING_SERVICE_FLAG);
}
/**
* Target Talkgroup or Radio Identifier
*/
public IntegerIdentifier getTargetAddress()
{
if(mTargetAddress == null)
{
if(isTalkgroupTarget())
{
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
}
else
{
mTargetAddress = DMRRadio.createTo(getMessage().getInt(TARGET_ADDRESS));
}
}
return mTargetAddress;
}
/**
* Indicates if the target address is a talkgroup (true) or radio (false).
*/
public boolean isTalkgroupTarget()
{
return getMessage().get(TALKGROUP_FLAG);
}
}

View File

@@ -0,0 +1,159 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Gateway;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III Ahoy - Authentication Challenge
*/
public class AuthenticateRegisterRadioCheck extends Ahoy
{
private List<Identifier> mIdentifiers;
private RadioIdentifier mSourceRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public AuthenticateRegisterRadioCheck(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" ").append(getCommand()).append(":").append(getTargetAddress());
if(isAuthenticateCommand())
{
sb.append(" CHALLENGE VALUE:").append(getChallengeValue());
}
return sb.toString();
}
/**
* Gateway address from the multi-purpose field
*/
public Gateway getGateway()
{
return Gateway.fromValue(getMultiPurposeFieldValue());
}
/**
* Command for this message
*/
public String getCommand()
{
switch(getGateway())
{
case TSI:
return "RADIO CHECK";
default:
return "AUTHENTICATE";
}
}
/**
* Indicates if this is an authentication command indicating that there is a challenge value.
*/
public boolean isAuthenticateCommand()
{
return getGateway() != Gateway.TSI;
}
/**
* Challenge value created by the base station to which the target address must authenticate.
*/
public String getChallengeValue()
{
return String.format("%06X", getMessage().getInt(MULTI_PURPOSE_FIELD));
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMultiPurposeFieldValue());
}
return mSourceRadio;
}
/**
* Indicates if this command includes a source radio (ie gateway) identifier
*/
public boolean hasSourceRadio()
{
return !isAuthenticateCommand();
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
if(hasSourceRadio())
{
mIdentifiers.add(getSourceRadio());
}
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,104 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III Ahoy - Cancel Call
*/
public class CancelCall extends Ahoy
{
private RadioIdentifier mSourceRadio;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CancelCall(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" CANCEL CALL TO:").append(getTargetAddress());
sb.append(" FM:").append(getSourceRadio());
return sb.toString();
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMultiPurposeFieldValue());
}
return mSourceRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,129 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III Ahoy - Voice/Data Service Radio Check for Individual/Talkgroup
*/
public class ServiceRadioCheck extends Ahoy
{
private RadioIdentifier mSourceRadio;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ServiceRadioCheck(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" ").append(getServiceDescription());
sb.append(" SERVICE RADIO CHECK (").append(isTalkgroupTarget() ? "TALKGROUP" : "INDIVIDUAL");
sb.append(") TO:").append(getTargetAddress());
sb.append(" FM:").append(getSourceRadio());
return sb.toString();
}
/**
* Description of the service type that is being checked
*/
public String getServiceDescription()
{
switch(getServiceKind())
{
case FULL_DUPLEX_MS_TO_MS_PACKET_CALL_SERVICE:
case INDIVIDUAL_PACKET_CALL_SERVICE:
case TALKGROUP_PACKET_CALL_SERVICE:
return "PACKET";
case INDIVIDUAL_UDT_SHORT_DATA_CALL_SERVICE:
case TALKGROUP_UDT_SHORT_DATA_CALL_SERVICE:
return "SHORT DATA";
case FULL_DUPLEX_MS_TO_MS_VOICE_CALL_SERVICE:
case INDIVIDUAL_VOICE_CALL_SERVICE:
case TALKGROUP_VOICE_CALL_SERVICE:
return "VOICE";
}
return "UNKNOWN";
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMultiPurposeFieldValue());
}
return mSourceRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,152 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Gateway;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III Ahoy - Stun, Revive or Kill a Mobile Radio
*/
public class StunReviveKill extends Ahoy
{
private RadioIdentifier mSourceRadio;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public StunReviveKill(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" ").append(getCommand()).append(" RADIO:").append(getTargetAddress());
sb.append(" FM:").append(getSourceRadio());
return sb.toString();
}
/**
* Gateway address from the multi-purpose field
*/
public Gateway getGateway()
{
return Gateway.fromValue(getMultiPurposeFieldValue());
}
/**
* Command for this message
*/
public String getCommand()
{
switch(getGateway())
{
case KILLI:
return "KILL";
case STUNI:
if(isStunCommand())
{
return "STUN";
}
else
{
return "REVIVE";
}
default:
return "UNKNOWN";
}
}
/**
* Indicates if this is a Stun command
*/
public boolean isStunCommand()
{
return !getMessage().get(SERVICE_KIND_FLAG);
}
/**
* Indicates if this is a Revive command
*/
public boolean isReviveCommand()
{
return !getMessage().get(SERVICE_KIND_FLAG);
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMultiPurposeFieldValue());
}
return mSourceRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,153 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.ahoy;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.type.Gateway;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III Ahoy - Unknown Ahoy Type
*/
public class UnknownAhoy extends Ahoy
{
private RadioIdentifier mSourceRadio;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public UnknownAhoy(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" AHOY ").append(getServiceKind());
sb.append(" TO:").append(getTargetAddress());
sb.append(" FM:").append(getSourceRadio());
return sb.toString();
}
/**
* Gateway address from the multi-purpose field
*/
public Gateway getGateway()
{
return Gateway.fromValue(getMultiPurposeFieldValue());
}
/**
* Command for this message
*/
public String getCommand()
{
switch(getGateway())
{
case KILLI:
return "KILL";
case STUNI:
if(isStunCommand())
{
return "STUN";
}
else
{
return "REVIVE";
}
default:
return "UNKNOWN";
}
}
/**
* Indicates if this is a Stun command
*/
public boolean isStunCommand()
{
return !getMessage().get(SERVICE_KIND_FLAG);
}
/**
* Indicates if this is a Revive command
*/
public boolean isReviveCommand()
{
return !getMessage().get(SERVICE_KIND_FLAG);
}
/**
* Source radio identifier
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMultiPurposeFieldValue());
}
return mSourceRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getTargetAddress());
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,272 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.announcement;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Announcement Message - Adjacent/Neighbor Site Information
*/
public class AdjacentSiteInformation extends Announcement implements ITimeslotFrequencyReceiver
{
private static final int NEIGHBOR_SYSTEM_IDENTITY_CODE_OFFSET = 21;
private static final int NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG = 56;
private static final int ACTIVE_NETWORK_CONNECTION_FLAG = 57;
private static final int[] CONFIRMED_CHANNEL_PRIORITY = new int[]{58, 59, 60};
private static final int[] ADJACENT_CHANNEL_PRIORITY = new int[]{61, 62, 63};
private static final int[] RESERVED = new int[]{64, 65, 66, 67};
private static final int[] NEIGHBOR_CHANNEL_NUMBER = new int[]{68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private SystemIdentityCode mNeighborSystemIdentityCode;
private List<Identifier> mIdentifiers;
private AbsoluteChannelParameters mAbsoluteChannelParameters;
private DMRChannel mChannel;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public AdjacentSiteInformation(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Constructs a multi-block MBC instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public AdjacentSiteInformation(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
SlotType slotType, long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
if(multiBlock != null)
{
//Timeslot hard-coded to zero for control channel
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
}
}
/**
* Checks CRC and sets the message valid flag according to the results.
*/
public void checkCRC()
{
if(getSlotType().getDataType() == DataType.MBC_HEADER)
{
checkMultiBlockCRC(getAbsoluteChannelParameters());
}
else
{
//Use the standard CSBK CRC check
super.checkCRC();
}
}
/**
* DMR Channel and Timeslot for neighbor site
*/
public DMRChannel getNeighborChannel()
{
if(hasAbsoluteChannelParameters())
{
return mAbsoluteChannelParameters.getChannel();
}
if(mChannel == null)
{
//Timeslot hard-coded to 1
mChannel = new DMRLogicalChannel(getMessage().getInt(NEIGHBOR_CHANNEL_NUMBER), 1);
}
return mChannel;
}
/**
* Absolute channel parameters structure
*/
public AbsoluteChannelParameters getAbsoluteChannelParameters()
{
return mAbsoluteChannelParameters;
}
/**
* Indicates if this message has an absolute channel parameters structure
*/
public boolean hasAbsoluteChannelParameters()
{
return getAbsoluteChannelParameters() != null;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NEIGHBOR NETWORK:").append(getNeighborSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getNeighborSystemIdentityCode().getSite());
sb.append(" ").append(getNeighborChannel());
sb.append(" THIS NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
return sb.toString();
}
/**
* Indicates if the active network connection status is available.
*
* See also: isActiveNetworkConnection()
*/
public boolean hasNetworkConnectionStatus()
{
return getMessage().get(NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG);
}
/**
* Indicates if this site has an active network connection
*
* See also: hasNetworkConnectionStatus()
*/
public boolean isActiveNetworkConnection()
{
return getMessage().get(ACTIVE_NETWORK_CONNECTION_FLAG);
}
/**
* Confirmed channel priority
*/
public int getConfirmedChannelPriority()
{
return getMessage().getInt(CONFIRMED_CHANNEL_PRIORITY);
}
/**
* Adjacent channel priority
*/
public int getAdjacentChannelPriority()
{
return getMessage().getInt(ADJACENT_CHANNEL_PRIORITY);
}
/**
* Control Channel Number for the neighbor site
*/
public int getNeighborChannelNumber()
{
return getMessage().getInt(NEIGHBOR_CHANNEL_NUMBER);
}
/**
* Neighbor Site System Identity Code structure
*/
public SystemIdentityCode getNeighborSystemIdentityCode()
{
if(mNeighborSystemIdentityCode == null)
{
mNeighborSystemIdentityCode = new SystemIdentityCode(getMessage(),
NEIGHBOR_SYSTEM_IDENTITY_CODE_OFFSET, false);
}
return mNeighborSystemIdentityCode;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
/**
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
{
if(getNeighborChannel() instanceof DMRLogicalChannel)
{
return ((DMRLogicalChannel)getNeighborChannel()).getLSNArray();
}
return new int[0];
}
/**
* Applies the timeslot frequency lookup information to channels contained in this message
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getNeighborChannel() instanceof DMRLogicalChannel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getNeighborChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
}
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.announcement;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.ArrayList;
import java.util.List;
/**
* Tier III Announcement - Logical channel:frequency relationship.
*/
public class AnnounceChannelFrequency extends Announcement
{
private AbsoluteChannelParameters mAbsoluteChannelParameters;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public AnnounceChannelFrequency(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
if(multiBlock != null)
{
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
}
}
/**
* Checks CRC and sets the message valid flag according to the results.
*/
public void checkCRC()
{
if(getSlotType().getDataType() == DataType.MBC_HEADER)
{
checkMultiBlockCRC(getAbsoluteChannelParameters());
}
else
{
//Use the standard CSBK CRC check
super.checkCRC();
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" ANNOUNCE CHANNEL");
if(hasAbsoluteChannelParameters())
{
sb.append(getAbsoluteChannelParameters().getChannel());
sb.append(" CC:").append(getAbsoluteChannelParameters().getColorCode());
}
return sb.toString();
}
/**
* Absolute channel parameters structure
*/
public AbsoluteChannelParameters getAbsoluteChannelParameters()
{
return mAbsoluteChannelParameters;
}
/**
* Indicates if this message has an absolute channel parameters structure
*/
public boolean hasAbsoluteChannelParameters()
{
return getAbsoluteChannelParameters() != null;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasAbsoluteChannelParameters())
{
mIdentifiers.add(getAbsoluteChannelParameters().getChannel());
}
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,256 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.announcement;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Announcement or Widthdraw TSCC
*/
public class AnnounceWithdrawTSCC extends Announcement
{
//Broadcast Parameters 1: 21-34
private static final int[] RESERVED = new int[]{21, 22, 23, 24};
private static final int[] COLOR_CODE_CHAN_1 = new int[]{25, 26, 27, 28};
private static final int[] COLOR_CODE_CHAN_2 = new int[]{29, 30, 31, 32};
private static final int ADD_WITHDRAW_CHAN_1_FLAG = 33;
private static final int ADD_WITHDRAW_CHAN_2_FLAG = 34;
//Broadcast Parameters 2: 56-79
private static final int[] CHANNEL_NUMBER_1 = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67};
private static final int[] CHANNEL_NUMBER_2 = new int[]{68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private DMRChannel mChannel1;
private DMRChannel mChannel2;
private AbsoluteChannelParameters mAbsoluteChannelParameters;
private List<Identifier> mIdentifiers;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public AnnounceWithdrawTSCC(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Constructs a multi-block MBC instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
* @param multiBlock containing absolute frequency parameters
*/
public AnnounceWithdrawTSCC(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
if(multiBlock != null)
{
//Control channel timeslot hard-coded to 0
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
}
}
/**
* Checks CRC and sets the message valid flag according to the results.
*/
public void checkCRC()
{
if(getSlotType().getDataType() == DataType.MBC_HEADER)
{
checkMultiBlockCRC(getAbsoluteChannelParameters());
}
else
{
//Use the standard CSBK CRC check
super.checkCRC();
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(hasChannel1())
{
sb.append((isChannel1Add() ? " ADD CHAN:" : " WITHDRAW CHAN:"));
sb.append(getChannel1()).append(" CC:").append(getChannel1ColorCode());
}
if(hasChannel2())
{
sb.append((isChannel2Add() ? " ADD CHAN:" : " WITHDRAW CHAN:"));
sb.append(getChannel2()).append(" CC:").append(getChannel2ColorCode());
}
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
return sb.toString();
}
/**
* Absolute channel parameters structure
*/
public AbsoluteChannelParameters getAbsoluteChannelParameters()
{
return mAbsoluteChannelParameters;
}
/**
* Indicates if this message has an absolute channel parameters structure
*/
public boolean hasAbsoluteChannelParameters()
{
return getAbsoluteChannelParameters() != null;
}
/**
* Indicates if this message has channel 1 information
*/
public boolean hasChannel1()
{
return hasAbsoluteChannelParameters() || getMessage().getInt(CHANNEL_NUMBER_1) != 0;
}
/**
* Indicates if this message has channel 2 information
*/
public boolean hasChannel2()
{
return getMessage().getInt(CHANNEL_NUMBER_2) != 0;
}
/**
* Color code for channel 1
*/
public int getChannel1ColorCode()
{
if(hasAbsoluteChannelParameters())
{
return getAbsoluteChannelParameters().getColorCode();
}
return getMessage().getInt(COLOR_CODE_CHAN_1);
}
/**
* Color code for channel 2
*/
public int getChannel2ColorCode()
{
return getMessage().getInt(COLOR_CODE_CHAN_2);
}
/**
* Indicates if channel 1 is being added (true) or withdrawn (false)
*/
public boolean isChannel1Add()
{
return getMessage().get(ADD_WITHDRAW_CHAN_1_FLAG);
}
/**
* Indicates if channel 2 is being added (true) or withdrawn (false)
*/
public boolean isChannel2Add()
{
return getMessage().get(ADD_WITHDRAW_CHAN_2_FLAG);
}
/**
* Channel 1
*/
public DMRChannel getChannel1()
{
if(mChannel1 == null)
{
if(hasAbsoluteChannelParameters())
{
mChannel1 = getAbsoluteChannelParameters().getChannel();
}
else
{
mChannel1 = new DMRLogicalChannel(getMessage().getInt(CHANNEL_NUMBER_1), 1);
}
}
return mChannel1;
}
/**
* Channel 2
*/
public DMRChannel getChannel2()
{
if(mChannel2 == null)
{
mChannel2 = new DMRLogicalChannel(getMessage().getInt(CHANNEL_NUMBER_2), 1);
}
return mChannel2;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,135 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.announcement;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Announcement Message
*/
public class Announcement extends CSBKMessage
{
private static final int[] ANNOUNCEMENT_TYPE = new int[]{16, 17, 18, 19, 20};
private static final int[] PARAMS_1 = new int[]{21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34};
private static final int REGISTRATION_REQUIRED_FLAG = 35;
private static final int[] BACKOFF = new int[]{36, 37, 38, 39};
private static final int SYSTEM_IDENTITY_CODE_OFFSET = 40;
private static final int[] PARAMS_2 = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
private SystemIdentityCode mSystemIdentityCode;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public Announcement(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" ANNOUNCEMENT ").append(getAnnouncementType());
return sb.toString();
}
/**
* Indicates if registration is required on this site
*/
public boolean isRegistrationRequired()
{
return getMessage().get(REGISTRATION_REQUIRED_FLAG);
}
/**
* Backoff value
*/
public int getBackoff()
{
return getMessage().getInt(BACKOFF);
}
/**
* System Identity Code structure
*/
public SystemIdentityCode getSystemIdentityCode()
{
if(mSystemIdentityCode == null)
{
mSystemIdentityCode = new SystemIdentityCode(getMessage(), SYSTEM_IDENTITY_CODE_OFFSET, true);
}
return mSystemIdentityCode;
}
/**
* Announcement message type for this message
*/
public AnnouncementType getAnnouncementType()
{
return getAnnouncementType(getMessage());
}
/**
* Utility to get announcement message type for the specified message
*/
public static AnnouncementType getAnnouncementType(CorrectedBinaryMessage message)
{
return AnnouncementType.fromValue(message.getInt(ANNOUNCEMENT_TYPE));
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,273 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.announcement;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Call Timer Parameters
*/
public class CallTimerParameters extends Announcement
{
//Broadcast Parameters 1: 21-34
private static final int[] EMERGENCY_TIMER = new int[]{21, 22, 23, 24, 25, 26, 27, 28, 29};
private static final int[] PACKET_TIMER = new int[]{30, 31, 32, 33, 34};
//Broadcast Parameters 2: 56-79
private static final int[] MOBILE_TO_MOBILE_TIMER = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67};
private static final int[] MOBILE_TO_LINE_TIMER = new int[]{68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public CallTimerParameters(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" CALL TIMERS EMERG:").append(getEmergencyTimerDecoded());
sb.append(" PACKET:").append(getPacketTimerDecoded());
sb.append(" MS-MS:").append(getMobileToMobileTimerDecoded());
sb.append(" MS-LINE:").append(getMobileToLineTimerDecoded());
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
return sb.toString();
}
/**
* Emergency Timer
* @return 0 = Internal, 1-510 = value, 511 = infinity
*/
public int getEmergencyTimer()
{
return getMessage().getInt(EMERGENCY_TIMER);
}
/**
* Decodes the numeric timer value for emergency calls
*/
public String getEmergencyTimerDecoded()
{
int timer = getEmergencyTimer();
if(timer == 0)
{
return "INTERNAL";
}
else if(1 <= timer && timer <= 10)
{
return timer + " SECS";
}
else if(11 <= timer && timer <= 20)
{
return ((timer - 8) * 5) + " SECS";
}
else if(21 <= timer && timer <= 28)
{
return ((timer - 16) * 15) + " SECS";
}
else if(29 <= timer && timer <= 40)
{
return ((timer - 22) * .5) + " MINS";
}
else if(41 <= timer && timer <= 51)
{
return (timer - 31) + " MINS";
}
else if(52 <= timer && timer <= 510)
{
return ((timer - 47) * 5) + " MINS";
}
return "INFINITY";
}
/**
* Packet Timer
* @return 0 = Internal, 1-30 = value, 31 = infinity
*/
public int getPacketTimer()
{
return getMessage().getInt(PACKET_TIMER);
}
/**
* Decodes the numeric timer value for packet calls
*/
public String getPacketTimerDecoded()
{
int timer = getPacketTimer();
if(timer == 0)
{
return "INTERNAL";
}
else if(1 <= timer && timer <= 5)
{
return timer + " SECS";
}
else if(6 <= timer && timer <= 10)
{
return ((timer - 4) * 5) + " SECS";
}
else if(11 <= timer && timer <= 12)
{
return ((timer - 8) * 15) + " SECS";
}
else if(13 <= timer && timer <= 20)
{
return ((timer - 10) * .5) + " MINS";
}
else if(21 <= timer && timer <= 25)
{
return (timer - 15) + " MINS";
}
else if(26 <= timer && timer <= 30)
{
return ((timer - 23) * 5) + " MINS";
}
return "INFINITY";
}
/**
* Mobile-to-Mobile Call Timer
* @return 0 = Internal, 1-4094 = value, 4095 = infinity
*/
public int getMobileToMobileTimer()
{
return getMessage().getInt(MOBILE_TO_MOBILE_TIMER);
}
/**
* Decodes the numeric timer value for mobile-2-mobile calls
*/
public String getMobileToMobileTimerDecoded()
{
int timer = getMobileToMobileTimer();
if(timer == 0)
{
return "INTERNAL";
}
else if(1 <= timer && timer <= 59)
{
return timer + " SECS";
}
else if(60 <= timer && timer <= 107)
{
return ((timer - 48) * 5) + " SECS";
}
else if(108 <= timer && timer <= 138)
{
return ((timer - 98) * .5) + " MINS";
}
else if(139 <= timer && timer <= 4094)
{
return (timer - 118) + " MINS";
}
return "INFINITY";
}
/**
* Mobile-to-Line Call Timer
* @return 0 = Internal, 1-510 = value, 511 = infinity
*/
public int getMobileToLineTimer()
{
return getMessage().getInt(MOBILE_TO_LINE_TIMER);
}
/**
* Decodes the numeric timer value for mobile-2-line calls
*/
public String getMobileToLineTimerDecoded()
{
int timer = getMobileToLineTimer();
if(timer == 0)
{
return "INTERNAL";
}
else if(1 <= timer && timer <= 59)
{
return timer + " SECS";
}
else if(60 <= timer && timer <= 107)
{
return ((timer - 48) * 5) + " SECS";
}
else if(108 <= timer && timer <= 138)
{
return ((timer - 98) * .5) + " MINS";
}
else if(139 <= timer && timer <= 4094)
{
return (timer - 118) + " MINS";
}
return "INFINITY";
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,122 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.announcement;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
/**
* DMR Tier III - Broadcast Local Time
*/
public class LocalTime extends Announcement
{
//Broadcast Parameters 1: 21-34
private static final int[] DAY = new int[]{21, 22, 23, 24, 25};
private static final int[] MONTH = new int[]{26, 27, 28, 39};
private static final int[] UTC = new int[]{30, 31, 32, 33, 34};
//Broadcast Parameters 2: 56-79
private static final int[] HOUR = new int[]{56, 57, 58, 59, 60};
private static final int[] MINUTE = new int[]{61, 62, 63, 64, 65, 66};
private static final int[] SECOND = new int[]{67, 68, 69, 70, 71, 72};
private static final int[] DAY_OF_WEEK = new int[]{73, 74, 75};
private static final int[] UTC_MINUTES_OFFSET = new int[]{76, 77};
private static final int[] RESERVED = new int[]{78, 79};
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public LocalTime(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" LOCAL TIME:").append(new Date(getLocalTime()).toString());
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
return sb.toString();
}
/**
* Local time broadcast
* @return
*/
public long getLocalTime()
{
Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.setTimeInMillis(System.currentTimeMillis());
int year = calendar.get(Calendar.YEAR);
int month = getMessage().getInt(MONTH);
int day = getMessage().getInt(DAY);
int utcHoursOffset = getMessage().getInt(UTC);
int hour = (getMessage().getInt(HOUR) + (utcHoursOffset == 31 ? 0 : utcHoursOffset)) % 24;
int minute = getMessage().getInt(MINUTE);
int second = getMessage().getInt(SECOND);
calendar.clear();
calendar.set(year, month, day, hour, minute, second);
return calendar.getTimeInMillis();
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,127 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.announcement;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Broadcast Mass Registration
*/
public class MassRegistration extends Announcement
{
//Broadcast Parameters 1: 21-34
private static final int[] RESERVED = new int[]{21, 22, 23, 24, 25};
private static final int[] REGISTRATION_WINDOW = new int[]{26, 27, 28, 39};
private static final int[] ALOHA_MASK = new int[]{30, 31, 32, 33, 34};
//Broadcast Parameters 2: 56-79
private static final int[] DESTINATION_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
private RadioIdentifier mDestinationRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public MassRegistration(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" MASS REGISTRATION");
if(hasDestinationRadio())
{
sb.append(" TO:").append(getDestinationRadio());
}
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
return sb.toString();
}
/**
* Indicates if this message is addressing a specific radio
*/
public boolean hasDestinationRadio()
{
return getMessage().getInt(DESTINATION_RADIO) != 0;
}
/**
* Addressed destination radio identifier
*/
public RadioIdentifier getDestinationRadio()
{
if(mDestinationRadio == null)
{
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
}
return mDestinationRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasDestinationRadio())
{
mIdentifiers.add(getDestinationRadio());
}
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,272 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.announcement;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Vote Now Advice
*/
public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyReceiver
{
//Broadcast Parameters 1: 21-34
private static final int VOTED_SYSTEM_IDENTITY_CODE_OFFSET = 21;
//Broadcast Parameters 2: 56-79
private static final int NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG = 56;
private static final int ACTIVE_NETWORK_CONNECTION_FLAG = 57;
private static final int[] CONFIRMED_CHANNEL_PRIORITY = new int[]{58, 59, 60};
private static final int[] ADJACENT_CHANNEL_PRIORITY = new int[]{61, 62, 63};
private static final int[] RESERVED = new int[]{64, 65, 66, 67};
private static final int[] VOTED_CHANNEL_NUMBER = new int[]{68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
private SystemIdentityCode mVotedSystemIdentityCode;
private List<Identifier> mIdentifiers;
private DMRChannel mChannel;
private AbsoluteChannelParameters mAbsoluteChannelParameters;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public VoteNowAdvice(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Constructs a multi-block MBC instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
* @param multiBlock containing absolute frequency parameters
*/
public VoteNowAdvice(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
if(multiBlock != null)
{
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
}
}
/**
* Checks CRC and sets the message valid flag according to the results.
*/
public void checkCRC()
{
if(getSlotType().getDataType() == DataType.MBC_HEADER)
{
checkMultiBlockCRC(getAbsoluteChannelParameters());
}
else
{
//Use the standard CSBK CRC check
super.checkCRC();
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" VOTED NETWORK:").append(getVotedSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getVotedSystemIdentityCode().getSite());
sb.append(" CHAN:").append(getChannel());
sb.append(" THIS ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
return sb.toString();
}
/**
* Indicates if the active network connection status is available.
*
* See also: isActiveNetworkConnection()
*/
public boolean hasNetworkConnectionStatus()
{
return getMessage().get(NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG);
}
/**
* Indicates if this site has an active network connection
*
* See also: hasNetworkConnectionStatus()
*/
public boolean isActiveNetworkConnection()
{
return getMessage().get(ACTIVE_NETWORK_CONNECTION_FLAG);
}
/**
* Confirmed channel priority
*/
public int getConfirmedChannelPriority()
{
return getMessage().getInt(CONFIRMED_CHANNEL_PRIORITY);
}
/**
* Adjacent channel priority
*/
public int getAdjacentChannelPriority()
{
return getMessage().getInt(ADJACENT_CHANNEL_PRIORITY);
}
/**
* Control Channel Number for the voted site
*/
public int getVotedChannelNumber()
{
return getMessage().getInt(VOTED_CHANNEL_NUMBER);
}
/**
* Absolute channel parameters structure
*/
public AbsoluteChannelParameters getAbsoluteChannelParameters()
{
return mAbsoluteChannelParameters;
}
/**
* Indicates if this message has an absolute channel parameters structure
*/
public boolean hasAbsoluteChannelParameters()
{
return getAbsoluteChannelParameters() != null;
}
/**
* DMR Channel and Timeslot for the channel grant
*/
public DMRChannel getChannel()
{
if(hasAbsoluteChannelParameters())
{
return mAbsoluteChannelParameters.getChannel();
}
if(mChannel == null)
{
mChannel = new DMRLogicalChannel(getMessage().getInt(VOTED_CHANNEL_NUMBER), 1);
}
return mChannel;
}
/**
* Voted Site System Identity Code structure
*/
public SystemIdentityCode getVotedSystemIdentityCode()
{
if(mVotedSystemIdentityCode == null)
{
mVotedSystemIdentityCode = new SystemIdentityCode(getMessage(), VOTED_SYSTEM_IDENTITY_CODE_OFFSET, false);
}
return mVotedSystemIdentityCode;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
/**
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
{
if(getChannel() instanceof DMRLogicalChannel)
{
return ((DMRLogicalChannel)getChannel()).getLSNArray();
}
return new int[0];
}
/**
* Applies the timeslot frequency lookup information to channels contained in this message
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getChannel() instanceof DMRLogicalChannel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
}
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import java.util.ArrayList;
import java.util.List;
/**
* Channel Grant - Voice - Broadcast Talkgroup
*/
public class BroadcastTalkgroupVoiceChannelGrant extends ChannelGrant
{
private static final int EMERGENCY_FLAG = 30;
private List<Identifier> mIdentifiers;
private RadioIdentifier mSourceRadio;
private TalkgroupIdentifier mDestinationTalkgroup;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public BroadcastTalkgroupVoiceChannelGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Constructs a multi-block MBC instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
* @param multiBlock containing absolute frequency parameters
*/
public BroadcastTalkgroupVoiceChannelGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
SlotType slotType, long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot, multiBlock);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEmergency())
{
sb.append(" EMERGENCY");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" BROADCAST TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
sb.append(" TO:").append(getDestinationTalkgroup());
sb.append(" CHAN:").append(getChannel());
return sb.toString();
}
/**
* Indicates if the emergency flag is set
*/
public boolean isEmergency()
{
return getMessage().get(EMERGENCY_FLAG);
}
/**
* Source radio identifier
* @return
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
}
return mSourceRadio;
}
/**
* Destination radio identifier
*/
public TalkgroupIdentifier getDestinationTalkgroup()
{
if(mDestinationTalkgroup == null)
{
mDestinationTalkgroup = DMRTalkgroup.create(getMessage().getInt(DESTINATION));
}
return mDestinationTalkgroup;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getChannel());
mIdentifiers.add(getSourceRadio());
mIdentifiers.add(getDestinationTalkgroup());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,191 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.List;
/**
* DMR Tier III - Channel Grant
*/
public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] CHANNEL_NUMBER = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
private static final int[] TIMESLOT = new int[]{28};
protected static final int[] DESTINATION = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55};
protected static final int[] SOURCE = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private DMRChannel mChannel;
private AbsoluteChannelParameters mAbsoluteChannelParameters;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public ChannelGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Constructs a multi-block MBC instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
* @param multiBlock continuation containing absolute channel parameters
*/
public ChannelGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
this(syncPattern, message, cach, slotType, timestamp, timeslot);
if(multiBlock != null)
{
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0,
getChannelGrantTimeslot());
}
}
/**
* Checks CRC and sets the message valid flag according to the results.
*/
public void checkCRC()
{
if(getSlotType().getDataType() == DataType.MBC_HEADER)
{
checkMultiBlockCRC(getAbsoluteChannelParameters());
}
else
{
//Use the standard CSBK CRC check
super.checkCRC();
}
}
/**
* Timeslot to use for the channel grant
*/
private int getChannelGrantTimeslot()
{
return getMessage().getInt(TIMESLOT) + 1;
}
/**
* Channel number for the channel grant
*/
private int getChannelNumber()
{
return getMessage().getInt(CHANNEL_NUMBER);
}
/**
* Absolute channel parameters structure
*/
public AbsoluteChannelParameters getAbsoluteChannelParameters()
{
return mAbsoluteChannelParameters;
}
/**
* Indicates if this message has an absolute channel parameters structure
*/
public boolean hasAbsoluteChannelParameters()
{
return getAbsoluteChannelParameters() != null;
}
/**
* DMR Channel and Timeslot for the channel grant
*/
public DMRChannel getChannel()
{
if(hasAbsoluteChannelParameters())
{
return mAbsoluteChannelParameters.getChannel();
}
if(mChannel == null)
{
mChannel = new DMRLogicalChannel(getChannelNumber(), getChannelGrantTimeslot());
}
return mChannel;
}
/**
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
{
if(getChannel() instanceof DMRLogicalChannel)
{
return ((DMRLogicalChannel)getChannel()).getLSNArray();
}
return new int[0];
}
/**
* Applies the timeslot frequency lookup information to channels contained in this message
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getChannel() instanceof DMRLogicalChannel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
}
}
}
}

View File

@@ -0,0 +1,168 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.dmr.message.data.csbk.standard.grant;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import java.util.ArrayList;
import java.util.List;
/**
* Duplex Channel Grant - Data - Private/Individual
*/
public class DuplexPrivateDataChannelGrant extends ChannelGrant
{
private static final int EMERGENCY_FLAG = 30;
private static final int TARGET_IS_SOURCE_ADDRESS_FLAG = 31;
private List<Identifier> mIdentifiers;
private RadioIdentifier mSourceRadio;
private RadioIdentifier mDestinationRadio;
/**
* Constructs a single-block CSBK instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public DuplexPrivateDataChannelGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
/**
* Constructs a multi-block MBC instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
* @param multiBlock containing absolute frequency parameters
*/
public DuplexPrivateDataChannelGrant(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
SlotType slotType, long timestamp, int timeslot, MBCContinuationBlock multiBlock)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot, multiBlock);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
if(isEmergency())
{
sb.append(" EMERGENCY");
}
if(isEncrypted())
{
sb.append(" ENCRYPTED");
}
sb.append(" DUPLEX PRIVATE DATA CHANNEL GRANT FM:").append(getSourceRadio());
sb.append(" TO:").append(getDestinationRadio());
sb.append(" CHAN:").append(getChannel());
return sb.toString();
}
/**
* Indicates if the emergency flag is set
*/
public boolean isEmergency()
{
return getMessage().get(EMERGENCY_FLAG);
}
/**
* Source radio identifier
* @return
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
if(getMessage().get(TARGET_IS_SOURCE_ADDRESS_FLAG))
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(DESTINATION));
}
else
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE));
}
}
return mSourceRadio;
}
/**
* Destination radio identifier
*/
public RadioIdentifier getDestinationRadio()
{
if(mDestinationRadio == null)
{
if(getMessage().get(TARGET_IS_SOURCE_ADDRESS_FLAG))
{
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(SOURCE));
}
else
{
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION));
}
}
return mDestinationRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getChannel());
mIdentifiers.add(getSourceRadio());
mIdentifiers.add(getDestinationRadio());
}
return mIdentifiers;
}
}

Some files were not shown because too many files have changed in this diff Show More