mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
* #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:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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()};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user