mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#2166 DMR adds RS(12,9) error detection and correction on link control. Enhances DMR CRC mask manager to process CSBK and link control messages to improve handling of RAS enabled systems when the user has not opted to ignore CRC checksums. Fixes issue with CapMax voice channels not resolving to frequency. Fixes issue in playlist editor where DMR LCN description values were not updating correctly. (#2200)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -19,148 +19,179 @@
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages alternate CRC masks that may be employed in a system. Some vendors employ non-standard CRC checksum masks
|
||||
* or initial fill values. This utility tracks these non-standard values by observation count and time and accepts
|
||||
* these alternate mask values to automatically correct or validate messages when those messages employ these
|
||||
* alternate mask values. Alternate mask values must be observed at least 3x times in a 2-minute window against the
|
||||
* same message content, in order for the alternate mask value to be used. Tracked values that do not get another
|
||||
* observation count within a 2-minute window will be ejected from the cache and excluded as a valid CRC mask candidate.
|
||||
*
|
||||
* Notes: different alternate CRC mask values can be used independently in each timeslot. Further, I've seen multiple
|
||||
* CRC mask values being employed within the same timeslot. This indicates that systems can use multiple CRC mask
|
||||
* values against CSBK messages and some other function is involved in determining which CRC mask value to use when
|
||||
* checking the CRC of the transmitted CSBK ... fun and games!
|
||||
* Tracks alternate CRC masks that may be employed in a system. Some vendors employ non-standard error detection and
|
||||
* correction checksum masks or initial fill values. This utility tracks these non-standard values by observation count
|
||||
* and time and accepts these alternate mask values to automatically correct or validate messages when those messages
|
||||
* employ these alternate mask values. Alternate mask values must be observed at least 2x times in a staleness
|
||||
* threshold window against the same message type and opcode, in order for the alternate mask value to be accepted as
|
||||
* valid. Tracked values that do not get another observation count within the staleness time threshold will be
|
||||
* ejected from the cache and excluded as a valid CRC mask candidate.
|
||||
*/
|
||||
public class DMRCrcMaskManager
|
||||
{
|
||||
private Logger LOGGER = LoggerFactory.getLogger(DMRCrcMaskManager.class);
|
||||
private Map<Integer, MaskTracker> mCsbkTrackerMapTS1 = new TreeMap<>();
|
||||
private Map<Integer, MaskTracker> mCsbkTrackerMapTS2 = new TreeMap<>();
|
||||
private Set<MaskTracker> mSortedTrackersTS1 = new TreeSet<>();
|
||||
private Set<MaskTracker> mSortedTrackersTS2 = new TreeSet<>();
|
||||
/**
|
||||
* Maximum time age of a tracked (alternate) mask value before it's ejected from the cache.
|
||||
*/
|
||||
private static final long CACHE_EJECTION_POLICY_TIME = TimeUnit.MINUTES.toMillis(2);
|
||||
|
||||
/**
|
||||
* Maximum staleness count before a mask is ejected from the cache. Staleness count is only incremented when the
|
||||
* specific type and opcode is encountered.
|
||||
*/
|
||||
private static final int CACHE_EJECTION_POLICY_COUNT = 20;
|
||||
|
||||
private final Map<Integer, OpcodeMaskTracker> mCsbkTrackerMap = new TreeMap<>();
|
||||
private final Map<Integer, OpcodeMaskTracker> mCrc5TrackerMap = new TreeMap<>();
|
||||
private final Map<Integer, OpcodeMaskTracker> mRs12_9TrackerMap = new TreeMap<>();
|
||||
private final boolean mIgnoreCRCChecksums;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param ignoreCRCChecksums value used to flag all error detection as valid.
|
||||
*/
|
||||
public DMRCrcMaskManager()
|
||||
public DMRCrcMaskManager(boolean ignoreCRCChecksums)
|
||||
{
|
||||
mIgnoreCRCChecksums = ignoreCRCChecksums;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the current tracked CRC values and counts.
|
||||
* Indicates if the decoder is configured to ignore invalid error detection and correction checksums.
|
||||
*/
|
||||
public void log()
|
||||
public boolean isIgnoreCRCChecksums()
|
||||
{
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("DMR CRC Mask Manager - TS1 Current Tracked Values\n");
|
||||
for(MaskTracker tracker: mSortedTrackersTS1)
|
||||
{
|
||||
sb.append("\tTracked Value: ").append(String.format("0x%04X", tracker.getTrackedValue()));
|
||||
sb.append(" Count:").append(tracker.getObservationCount());
|
||||
sb.append(" Last Observed: " + new Date(tracker.getLastUpdated())).append("\n");
|
||||
}
|
||||
|
||||
sb.append("DMR CRC Mask Manager - TS2 Current Tracked Values\n");
|
||||
for(MaskTracker tracker: mSortedTrackersTS2)
|
||||
{
|
||||
sb.append("\tTracked Value: ").append(String.format("0x%04X", tracker.getTrackedValue()));
|
||||
sb.append(" Count:").append(tracker.getObservationCount());
|
||||
sb.append(" Last Observed: " + new Date(tracker.getLastUpdated())).append("\n");
|
||||
}
|
||||
|
||||
LOGGER.info(sb.toString());
|
||||
return mIgnoreCRCChecksums;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the CSBK message that has a failed CRC checksum to detect if an alternate CRC mask value is employed and
|
||||
* if so, attempts to correct the message.
|
||||
* @param csbk to re-check using the currently tracked mask value.
|
||||
* Indicates if the residual value is commonly seen for this opcode.
|
||||
* @param opcode of the link control message
|
||||
* @param residual from the RS(12,9) check
|
||||
* @param timestamp of the message
|
||||
* @return true if this value is repeatedly seen for this opcode.
|
||||
*/
|
||||
public void check(CSBKMessage csbk)
|
||||
public boolean isValidRS12_9(int opcode, int residual, long timestamp)
|
||||
{
|
||||
if(!csbk.isValid())
|
||||
return mIgnoreCRCChecksums || isValid(opcode, residual, timestamp, mRs12_9TrackerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the residual value is commonly seen for this opcode.
|
||||
* @param opcode of the link control message
|
||||
* @param residual from the CRC-5 check
|
||||
* @param timestamp of the message
|
||||
* @return true if this value is repeatedly seen for this opcode.
|
||||
*/
|
||||
public boolean isValidCRC5(int opcode, int residual, long timestamp)
|
||||
{
|
||||
return mIgnoreCRCChecksums || isValid(opcode, residual, timestamp, mCrc5TrackerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the residual value is commonly seen for this opcode.
|
||||
* @param opcode of the link control message
|
||||
* @param residual from the CRC-5 check
|
||||
* @param timestamp of the message
|
||||
* @return true if this value is repeatedly seen for this opcode.
|
||||
*/
|
||||
public boolean isValidCSBK(int opcode, int residual, long timestamp)
|
||||
{
|
||||
return mIgnoreCRCChecksums || isValid(opcode, residual, timestamp, mCsbkTrackerMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the opcode and residual mask value have been seen recently, multiple times.
|
||||
* @param opcode to check
|
||||
* @param residual from the CRC or RS checksum.
|
||||
* @param timestamp of the message
|
||||
* @param trackerMap to check
|
||||
* @return true if the opcode and residual have been seen recently, multiple times.
|
||||
*/
|
||||
private boolean isValid(int opcode, int residual, long timestamp, Map<Integer, OpcodeMaskTracker> trackerMap)
|
||||
{
|
||||
if(opcode >= 0)
|
||||
{
|
||||
int alternateMask = CRCDMR.calculateResidual(csbk.getMessage(), 0, 80);
|
||||
OpcodeMaskTracker tracker = trackerMap.get(opcode);
|
||||
|
||||
Map<Integer,MaskTracker> map;
|
||||
Set<MaskTracker> sortedSet;
|
||||
|
||||
if(csbk.getTimeslot() == 1)
|
||||
if(tracker != null)
|
||||
{
|
||||
map = mCsbkTrackerMapTS1;
|
||||
sortedSet = mSortedTrackersTS1;
|
||||
return tracker.isValid(residual, timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
map = mCsbkTrackerMapTS2;
|
||||
sortedSet = mSortedTrackersTS2;
|
||||
trackerMap.put(opcode, new OpcodeMaskTracker(residual, timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
if(map.containsKey(alternateMask))
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks a set of mask values for an opcode value.
|
||||
*/
|
||||
public static class OpcodeMaskTracker
|
||||
{
|
||||
private final Map<Integer, MaskTracker> mTrackerMap = new TreeMap<>();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param mask to track for the opcode
|
||||
* @param timestamp when the mask was detected, for tracking staleness
|
||||
*/
|
||||
public OpcodeMaskTracker(int mask, long timestamp)
|
||||
{
|
||||
mTrackerMap.put(mask, new MaskTracker(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the mask is valid by checking for multiple recent usage.
|
||||
* @param mask to check
|
||||
* @param timestamp when the mask was used
|
||||
* @return true if the mask has been seen multiple times recently
|
||||
*/
|
||||
public boolean isValid(int mask, long timestamp)
|
||||
{
|
||||
boolean valid = false;
|
||||
|
||||
if(mTrackerMap.containsKey(mask))
|
||||
{
|
||||
MaskTracker matchingTracker = map.get(alternateMask);
|
||||
matchingTracker.increment(csbk.getTimestamp());
|
||||
|
||||
if(matchingTracker.isValid())
|
||||
{
|
||||
csbk.checkCRC(matchingTracker.getTrackedValue());
|
||||
}
|
||||
MaskTracker matchingTracker = mTrackerMap.get(mask);
|
||||
matchingTracker.increment(timestamp);
|
||||
valid = matchingTracker.isValid();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Attempt to use other tracked mask values in order of observed count and also remove stale trackers
|
||||
boolean found = false;
|
||||
MaskTracker next;
|
||||
Iterator<MaskTracker> it = sortedSet.iterator();
|
||||
mTrackerMap.put(mask, new MaskTracker(timestamp));
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Integer,MaskTracker>> it = mTrackerMap.entrySet().iterator();
|
||||
{
|
||||
while(it.hasNext())
|
||||
{
|
||||
next = it.next();
|
||||
|
||||
//If the tracker is stale, remove it
|
||||
if(next.isStale(csbk.getTimestamp()))
|
||||
if(it.next().getValue().isStale(timestamp))
|
||||
{
|
||||
it.remove();
|
||||
map.remove(next.getTrackedValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we haven't found a match yet, attempt to correct the message using this tracked mask value
|
||||
if(!found)
|
||||
{
|
||||
csbk.checkCRC(next.getTrackedValue());
|
||||
|
||||
if(csbk.isValid())
|
||||
{
|
||||
found = true;
|
||||
next.increment(csbk.getTimestamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If we didn't find a perfect match or even a close match (with 1 bit error), create a new tracked value.
|
||||
if(!found)
|
||||
{
|
||||
MaskTracker tracker = new MaskTracker(alternateMask, csbk.getTimestamp());
|
||||
map.put(alternateMask, tracker);
|
||||
sortedSet.add(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for(Map.Entry<Integer,MaskTracker> entry: mTrackerMap.entrySet())
|
||||
{
|
||||
sb.append("\t").append("Mask [").append(entry.getKey()).append("] ").append(entry.getValue()).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,11 +199,8 @@ public class DMRCrcMaskManager
|
||||
* Alternate CRC mask value tracker. Tracks the number of observances of the mask value to detect when an alternate
|
||||
* CRC mask pattern is employed and can then be used to automatically correct CRC values for messages.
|
||||
*/
|
||||
public class MaskTracker implements Comparable<MaskTracker>
|
||||
public static class MaskTracker implements Comparable<MaskTracker>
|
||||
{
|
||||
private static final long STALENESS_TIME_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
|
||||
private static final int STALENESS_COUNT_THRESHOLD = 100;
|
||||
private int mTrackedValue;
|
||||
private int mObservationCount;
|
||||
private int mStalenessCount;
|
||||
private long mLastUpdated;
|
||||
@@ -180,16 +208,20 @@ public class DMRCrcMaskManager
|
||||
/**
|
||||
* Constructs an instance for the specified mask, sets the observation count to one and sets the last update to
|
||||
* the supplied timestamp.
|
||||
* @param residual value to track.
|
||||
* @param timestamp for the observation
|
||||
*/
|
||||
public MaskTracker(int residual, long timestamp)
|
||||
public MaskTracker(long timestamp)
|
||||
{
|
||||
mTrackedValue = residual;
|
||||
mObservationCount = 1;
|
||||
mLastUpdated = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Observed:" + mObservationCount + " Staleness:" + mStalenessCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of observations for this mask value.
|
||||
* @return count
|
||||
@@ -200,13 +232,13 @@ public class DMRCrcMaskManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the residual value has been observed at least 3x times within the preceding 2 minute window,
|
||||
* Indicates that the residual value has been observed at least 3x times within the preceding 2-minute window,
|
||||
* indicating that it is (likely) valid.
|
||||
* @return true if the tracked mask value is valid.
|
||||
*/
|
||||
public boolean isValid()
|
||||
{
|
||||
return mObservationCount >= 3;
|
||||
return mObservationCount >= 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,8 +251,8 @@ public class DMRCrcMaskManager
|
||||
public boolean isStale(long timestamp)
|
||||
{
|
||||
mStalenessCount++;
|
||||
return mStalenessCount > STALENESS_COUNT_THRESHOLD ||
|
||||
Math.abs(timestamp - mLastUpdated) > STALENESS_TIME_THRESHOLD;
|
||||
return mStalenessCount > CACHE_EJECTION_POLICY_COUNT ||
|
||||
Math.abs(timestamp - mLastUpdated) > CACHE_EJECTION_POLICY_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,15 +263,6 @@ public class DMRCrcMaskManager
|
||||
return mLastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracked alternate mask value.
|
||||
* @return tracked mask value
|
||||
*/
|
||||
public int getTrackedValue()
|
||||
{
|
||||
return mTrackedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the observation count and use the supplied timestamp as the last updated timestamp.
|
||||
* @param timestamp for the observed message.
|
||||
@@ -248,7 +271,7 @@ public class DMRCrcMaskManager
|
||||
{
|
||||
mStalenessCount = 0;
|
||||
mObservationCount++;
|
||||
//Detect integer rollover and apply max integer value.
|
||||
//Detect integer rollover and adjust to max integer value.
|
||||
if(mObservationCount < 0)
|
||||
{
|
||||
mObservationCount = Integer.MAX_VALUE;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -86,12 +86,12 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
private static final int SYMBOL_RATE = 4800;
|
||||
private static final Map<Double,float[]> BASEBAND_FILTERS = new HashMap<>();
|
||||
private DQPSKDemodulator mDemodulator;
|
||||
private DMRMessageFramer mMessageFramer = new DMRMessageFramer();
|
||||
private DMRSoftSymbolProcessor mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer);
|
||||
private DMRMessageProcessor mMessageProcessor;
|
||||
private final DMRMessageFramer mMessageFramer;
|
||||
private final DMRSoftSymbolProcessor mSymbolProcessor;
|
||||
private final DMRMessageProcessor mMessageProcessor;
|
||||
private IRealFilter mIBasebandFilter;
|
||||
private IRealFilter mQBasebandFilter;
|
||||
private PowerMonitor mPowerMonitor = new PowerMonitor();
|
||||
private final PowerMonitor mPowerMonitor = new PowerMonitor();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -99,7 +99,10 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
*/
|
||||
public DMRDecoder(DecodeConfigDMR config, boolean isTrafficChannel)
|
||||
{
|
||||
mMessageProcessor = new DMRMessageProcessor(config);
|
||||
DMRCrcMaskManager crcMaskManager = new DMRCrcMaskManager(config.getIgnoreCRCChecksums());
|
||||
mMessageFramer = new DMRMessageFramer(crcMaskManager);
|
||||
mSymbolProcessor = new DMRSoftSymbolProcessor(mMessageFramer);
|
||||
mMessageProcessor = new DMRMessageProcessor(config, crcMaskManager);
|
||||
mMessageProcessor.setMessageListener(getMessageListener());
|
||||
setSampleRate(25000.0);
|
||||
|
||||
@@ -259,11 +262,9 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
*/
|
||||
private void process(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
if(sourceEvent != null && sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_SAMPLE_RATE_CHANGE)
|
||||
{
|
||||
case NOTIFICATION_SAMPLE_RATE_CHANGE:
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
break;
|
||||
setSampleRate(sourceEvent.getValue().doubleValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,10 +330,6 @@ public class DMRDecoder extends Decoder implements IByteBufferProvider, IComplex
|
||||
errors = burst.getMessage().getCorrectedBitCount();
|
||||
mTotalMessageCounter++;
|
||||
|
||||
if(mTotalMessageCounter == 492)
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
if(burst.isValid())
|
||||
{
|
||||
mBitErrorCounter += Math.max(errors, 0);
|
||||
|
||||
@@ -150,9 +150,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
|
||||
//For RAS protected systems, allows user to ignore CRC checksums and still decode the system
|
||||
if(channel.getDecodeConfiguration() instanceof DecodeConfigDMR)
|
||||
if(channel.getDecodeConfiguration() instanceof DecodeConfigDMR config)
|
||||
{
|
||||
mIgnoreCRCChecksums = ((DecodeConfigDMR)channel.getDecodeConfiguration()).getIgnoreCRCChecksums();
|
||||
mIgnoreCRCChecksums = config.getIgnoreCRCChecksums();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,7 +857,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
//Channel rotation monitor normally uses only CONTROL state, so when we detect that we're a
|
||||
//Capacity plus system, add ACTIVE as an active state to the monitor. This can be requested repeatedly.
|
||||
getInterModuleEventBus().post(CAPACITY_PLUS_ACTIVE_STATE_REQUEST);
|
||||
if(getInterModuleEventBus() != null)
|
||||
{
|
||||
getInterModuleEventBus().post(CAPACITY_PLUS_ACTIVE_STATE_REQUEST);
|
||||
}
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -35,7 +35,7 @@ import java.util.Arrays;
|
||||
* DMR message framer. Processes a DMR symbol stream to produce DMR messages/bursts. Employs two symbol buffers to
|
||||
* track and frame both DMR timeslots. Responds to external sync detections to alternately activate one of the two
|
||||
* buffers and capture a DMR burst, convert it to the correct message format and send it to a message listener.
|
||||
*
|
||||
* <p>
|
||||
* This framer is able to detect DMR Direct mode and track one active and one inactive timeslot across each voice
|
||||
* super frame in addition to framing base and mobile station data and voice bursts.
|
||||
*/
|
||||
@@ -44,8 +44,8 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
private static final int DIBIT_CACH_START = 0;
|
||||
private static final int DIBIT_BURST_END = 144;
|
||||
private Listener<IMessage> mMessageListener;
|
||||
private Dibit[] mBufferA = new Dibit[144];
|
||||
private Dibit[] mBufferB = new Dibit[144];
|
||||
private final Dibit[] mBufferA = new Dibit[144];
|
||||
private final Dibit[] mBufferB = new Dibit[144];
|
||||
private int mBufferAPointer = 0;
|
||||
private int mBufferBPointer = 0;
|
||||
private DMRSyncPattern mBufferAPattern = DMRSyncPattern.UNKNOWN;
|
||||
@@ -58,14 +58,16 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
private int mDibitSinceTimestampCounter = 0;
|
||||
private long mReferenceTimestamp = 0;
|
||||
private boolean mRunning = false;
|
||||
private final DMRMessageFactory mMessageFactory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DMRMessageFramer()
|
||||
public DMRMessageFramer(DMRCrcMaskManager crcMaskManager)
|
||||
{
|
||||
Arrays.fill(mBufferA, Dibit.D00_PLUS_1);
|
||||
Arrays.fill(mBufferB, Dibit.D00_PLUS_1);
|
||||
mMessageFactory = new DMRMessageFactory(crcMaskManager);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,7 +140,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
|
||||
mDibitCounter = 0;
|
||||
|
||||
CorrectedBinaryMessage message = getMessage(mBufferA, DIBIT_CACH_START, DIBIT_BURST_END);
|
||||
CorrectedBinaryMessage message = getMessage(mBufferA);
|
||||
CACH cach = CACH.getCACH(message);
|
||||
|
||||
if(mBufferAPattern.hasCACH() && cach.isValid() && mBufferATimeslot != cach.getTimeslot())
|
||||
@@ -156,7 +158,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(DMRMessageFactory.create(mBufferAPattern, message, cach, getTimestamp(), mBufferATimeslot));
|
||||
dispatch(mMessageFactory.create(mBufferAPattern, message, cach, getTimestamp(), mBufferATimeslot));
|
||||
|
||||
//Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts
|
||||
//across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync.
|
||||
@@ -206,7 +208,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
|
||||
mDibitCounter = 0;
|
||||
|
||||
CorrectedBinaryMessage burst = getMessage(mBufferB, DIBIT_CACH_START, DIBIT_BURST_END);
|
||||
CorrectedBinaryMessage burst = getMessage(mBufferB);
|
||||
CACH cach = CACH.getCACH(burst);
|
||||
|
||||
if(mBufferBPattern.hasCACH() && cach.isValid() && mBufferBTimeslot != cach.getTimeslot())
|
||||
@@ -225,7 +227,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(DMRMessageFactory.create(mBufferBPattern, burst, cach, getTimestamp(), mBufferBTimeslot));
|
||||
dispatch(mMessageFactory.create(mBufferBPattern, burst, cach, getTimestamp(), mBufferBTimeslot));
|
||||
|
||||
//Since voice frames only have sync on the first burst, we use pseudo-patterns to track the rest of the bursts
|
||||
//across the voice super-frame. If the current pattern is a voice frame, set it to the next pseudo voice sync.
|
||||
@@ -249,7 +251,7 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
|
||||
/**
|
||||
* Dispatches the message to the registered message listener.
|
||||
* @param message
|
||||
* @param message to dispatch
|
||||
*/
|
||||
private void dispatch(IMessage message)
|
||||
{
|
||||
@@ -286,17 +288,16 @@ public class DMRMessageFramer implements Listener<Dibit>
|
||||
|
||||
/**
|
||||
* Creates a binary message from the dibit buffer.
|
||||
*
|
||||
* @param buffer containing dibits
|
||||
* @param start index inclusive
|
||||
* @param end index exclusive.
|
||||
* @return binary message
|
||||
*/
|
||||
private CorrectedBinaryMessage getMessage(Dibit[] buffer, int start, int end)
|
||||
private CorrectedBinaryMessage getMessage(Dibit[] buffer)
|
||||
{
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(2 * (end - start));
|
||||
CorrectedBinaryMessage message = new CorrectedBinaryMessage(2 * (DMRMessageFramer.DIBIT_BURST_END - DMRMessageFramer.DIBIT_CACH_START));
|
||||
|
||||
Dibit dibit = null;
|
||||
for(int i = start; i < end; i++)
|
||||
Dibit dibit;
|
||||
for(int i = DMRMessageFramer.DIBIT_CACH_START; i < DMRMessageFramer.DIBIT_BURST_END; i++)
|
||||
{
|
||||
dibit = buffer[i];
|
||||
message.add(dibit.getBit1(), dibit.getBit2());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.dmr;
|
||||
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.SyncLossMessage;
|
||||
@@ -63,28 +64,31 @@ import org.slf4j.LoggerFactory;
|
||||
public class DMRMessageProcessor implements Listener<IMessage>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
||||
private DecodeConfigDMR mConfigDMR;
|
||||
private VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
|
||||
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
|
||||
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
||||
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
||||
private MBCAssembler mMBCAssembler;
|
||||
private PacketSequenceAssembler mPacketSequenceAssembler;
|
||||
private SLCAssembler mSLCAssembler = new SLCAssembler();
|
||||
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
|
||||
private final DecodeConfigDMR mConfigDMR;
|
||||
private final VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
|
||||
private final VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
|
||||
private final FLCAssembler mFLCAssemblerTimeslot1;
|
||||
private final FLCAssembler mFLCAssemblerTimeslot2;
|
||||
private final MBCAssembler mMBCAssembler;
|
||||
private final PacketSequenceAssembler mPacketSequenceAssembler;
|
||||
private final SLCAssembler mSLCAssembler = new SLCAssembler();
|
||||
private final TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
|
||||
private Listener<IMessage> mMessageListener;
|
||||
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
||||
private DMRCrcMaskManager mCrcMaskManager = new DMRCrcMaskManager();
|
||||
private boolean mIgnoreCrcChecksums;
|
||||
private final Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
||||
private final DMRCrcMaskManager mCrcMaskManager;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param config for the DMR decoder
|
||||
* @param crcMaskManager for message error detection and correction.
|
||||
*/
|
||||
public DMRMessageProcessor(DecodeConfigDMR config)
|
||||
public DMRMessageProcessor(DecodeConfigDMR config, DMRCrcMaskManager crcMaskManager)
|
||||
{
|
||||
mConfigDMR = config;
|
||||
mIgnoreCrcChecksums = config.getIgnoreCRCChecksums();
|
||||
mMBCAssembler = new MBCAssembler(mIgnoreCrcChecksums);
|
||||
mCrcMaskManager = crcMaskManager;
|
||||
mMBCAssembler = new MBCAssembler(crcMaskManager.isIgnoreCRCChecksums());
|
||||
mFLCAssemblerTimeslot1 = new FLCAssembler(1, crcMaskManager);
|
||||
mFLCAssemblerTimeslot2 = new FLCAssembler(2, crcMaskManager);
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
|
||||
{
|
||||
@@ -102,7 +106,7 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
*/
|
||||
private boolean isValid(IMessage message)
|
||||
{
|
||||
return mIgnoreCrcChecksums || message.isValid();
|
||||
return mCrcMaskManager.isIgnoreCRCChecksums() || message.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,10 +127,12 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
mFLCAssemblerTimeslot2.reset();
|
||||
}
|
||||
|
||||
//Detect and correct messages employing an alternate CRC mask pattern.
|
||||
if(!message.isValid() && message instanceof CSBKMessage csbk)
|
||||
//Detect and correct messages employing an alternate CRC mask pattern (ie RAS) when ignore CRC is disabled
|
||||
if(message instanceof CSBKMessage csbk && !message.isValid())
|
||||
{
|
||||
mCrcMaskManager.check(csbk);
|
||||
int opcode = csbk.getOpcode().getValue();
|
||||
int residual = CRCDMR.calculateResidual(csbk.getMessage(), 0, 80);
|
||||
csbk.setValid(mCrcMaskManager.isValidCSBK(opcode, residual, csbk.getTimestamp()));
|
||||
}
|
||||
|
||||
if(message instanceof FullLCMessage flc)
|
||||
@@ -199,10 +205,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
}
|
||||
//Extract the Short Link Control message fragment from the DMR burst message when it has one
|
||||
else if(message instanceof DMRBurst)
|
||||
else if(message instanceof DMRBurst dmrBurst)
|
||||
{
|
||||
DMRBurst dmrBurst = (DMRBurst)message;
|
||||
|
||||
if(dmrBurst.hasCACH())
|
||||
{
|
||||
CACH cach = dmrBurst.getCACH();
|
||||
@@ -272,9 +276,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
*/
|
||||
private void enrich(IMessage message)
|
||||
{
|
||||
if(message instanceof ITimeslotFrequencyReceiver)
|
||||
if(message instanceof ITimeslotFrequencyReceiver receiver)
|
||||
{
|
||||
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
||||
int[] lcns = receiver.getLogicalChannelNumbers();
|
||||
|
||||
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
||||
@@ -316,8 +319,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error applying compressed talkgroup setting to identifier in message: " + message +
|
||||
" [" + message.getClass() + "]");
|
||||
mLog.error("Error applying compressed talkgroup setting to identifier in message: {} [{}]",
|
||||
message, message.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -28,6 +28,8 @@ import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.util.Callback;
|
||||
|
||||
/**
|
||||
@@ -40,6 +42,7 @@ public class TimeslotFrequency
|
||||
private LongProperty mUplinkFrequencyProperty = new SimpleLongProperty();
|
||||
private DoubleProperty mDownlinkMhzProperty = new SimpleDoubleProperty();
|
||||
private DoubleProperty mUplinkMhzProperty = new SimpleDoubleProperty();
|
||||
private StringProperty mDescriptionProperty = new SimpleStringProperty();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -60,6 +63,12 @@ public class TimeslotFrequency
|
||||
return copy;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public StringProperty descriptionProperty()
|
||||
{
|
||||
return mDescriptionProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical Slot Number property
|
||||
*/
|
||||
@@ -122,7 +131,7 @@ public class TimeslotFrequency
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getLogicalSlotNumbers()
|
||||
public void updateDescription()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -148,7 +157,25 @@ public class TimeslotFrequency
|
||||
sb.append("(empty)");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
setDescription(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of the logical slot numbers for the channel number.
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getDescription()
|
||||
{
|
||||
return mDescriptionProperty.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of the timeslots for the channel number.
|
||||
* @param description of the logical timeslots for the channel number.
|
||||
*/
|
||||
public void setDescription(String description)
|
||||
{
|
||||
mDescriptionProperty.set(description);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,6 +185,7 @@ public class TimeslotFrequency
|
||||
public void setNumber(int lcn)
|
||||
{
|
||||
mNumberProperty.set(lcn);
|
||||
updateDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,6 +240,6 @@ public class TimeslotFrequency
|
||||
public static Callback<TimeslotFrequency,Observable[]> extractor()
|
||||
{
|
||||
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyProperty(),
|
||||
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
|
||||
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz(), tf.descriptionProperty()};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -22,6 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.message.EmptyTimeslotPlaceholderMessage;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
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;
|
||||
@@ -31,9 +32,22 @@ import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* DMR Message Factory class for creating messages from DMR bursts.
|
||||
*/
|
||||
public class DMRMessageFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageFactory.class);
|
||||
private DMRDataMessageFactory mDataMessageFactory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param crcMaskManager for error detection and correction.
|
||||
*/
|
||||
public DMRMessageFactory(DMRCrcMaskManager crcMaskManager)
|
||||
{
|
||||
mDataMessageFactory = new DMRDataMessageFactory(crcMaskManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DMR message wrapper for the binary message. DMR message type is specified by the sync pattern.
|
||||
@@ -43,8 +57,8 @@ public class DMRMessageFactory
|
||||
* @param timeslot for the burst
|
||||
* @return DMRMessage instance or null for UNKNOWN sync pattern
|
||||
*/
|
||||
public static IMessage create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach,
|
||||
long timestamp, int timeslot)
|
||||
public IMessage create(DMRSyncPattern syncPattern, CorrectedBinaryMessage binaryMessage, CACH cach, long timestamp,
|
||||
int timeslot)
|
||||
{
|
||||
switch(syncPattern)
|
||||
{
|
||||
@@ -60,11 +74,11 @@ public class DMRMessageFactory
|
||||
return createVoiceMessage(syncPattern, binaryMessage, cach, timestamp, 2);
|
||||
case BASE_STATION_DATA:
|
||||
case MOBILE_STATION_DATA:
|
||||
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, timeslot);
|
||||
return mDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, timeslot);
|
||||
case DIRECT_DATA_TIMESLOT_1:
|
||||
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 1);
|
||||
return mDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 1);
|
||||
case DIRECT_DATA_TIMESLOT_2:
|
||||
return DMRDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 2);
|
||||
return mDataMessageFactory.create(syncPattern, binaryMessage, cach, timestamp, 2);
|
||||
case DIRECT_EMPTY_TIMESLOT:
|
||||
return new EmptyTimeslotPlaceholderMessage(timestamp, 288, Protocol.DMR, timeslot);
|
||||
case UNKNOWN:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.bits.BitSetFullException;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.edac.trellis.ViterbiDecoder_3_4_DMR;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
import io.github.dsheirer.module.decode.dmr.bptc.BPTC_196_96;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock1Rate;
|
||||
@@ -45,6 +46,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||
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.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;
|
||||
@@ -62,6 +65,16 @@ 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();
|
||||
private final LCMessageFactory mLinkControlMessageFactory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param maskManager for managing CSBK and link control error detection.
|
||||
*/
|
||||
public DMRDataMessageFactory(DMRCrcMaskManager maskManager)
|
||||
{
|
||||
mLinkControlMessageFactory = new LCMessageFactory(maskManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a data message class
|
||||
@@ -72,8 +85,8 @@ public class DMRDataMessageFactory
|
||||
* @param timeslot for the message
|
||||
* @return data message instance
|
||||
*/
|
||||
public static DataMessage create(DMRSyncPattern pattern, CorrectedBinaryMessage message, CACH cach, long timestamp,
|
||||
int timeslot)
|
||||
public DataMessage create(DMRSyncPattern pattern, CorrectedBinaryMessage message, CACH cach, long timestamp,
|
||||
int timeslot)
|
||||
{
|
||||
SlotType slotType = SlotType.getSlotType(message);
|
||||
|
||||
@@ -107,18 +120,12 @@ public class DMRDataMessageFactory
|
||||
}
|
||||
return csbk;
|
||||
case USB_DATA:
|
||||
CorrectedBinaryMessage usbdPayload = payload;
|
||||
switch(USBData.getServiceType(usbdPayload))
|
||||
DataMessage usb = new USBData(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
case LIP_SHORT_LOCATION_REQUEST:
|
||||
default:
|
||||
DataMessage usb =new USBData(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
usb.setValid(false);
|
||||
}
|
||||
return usb;
|
||||
usb.setValid(false);
|
||||
}
|
||||
return usb;
|
||||
case MBC_ENC_HEADER:
|
||||
case MBC_HEADER:
|
||||
DataMessage mbc = new MBCHeader(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
@@ -129,14 +136,18 @@ public class DMRDataMessageFactory
|
||||
return mbc;
|
||||
case CHANNEL_CONTROL_ENC_HEADER:
|
||||
case PI_HEADER:
|
||||
DataMessage pi = new PiHeader(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
LCMessage piLinkControl = mLinkControlMessageFactory.createFullEncryption(payload, timestamp,
|
||||
timeslot);
|
||||
DataMessage pi = new PiHeader(pattern, payload, cach, slotType, timestamp, timeslot, piLinkControl);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
pi.setValid(false);
|
||||
}
|
||||
return pi;
|
||||
case VOICE_HEADER:
|
||||
DataMessage voice = new VoiceHeader(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
LCMessage vhLinkControl = mLinkControlMessageFactory.createFull(payload, timestamp, timeslot, false);
|
||||
DataMessage voice = new VoiceHeader(pattern, payload, cach, slotType, timestamp, timeslot,
|
||||
vhLinkControl);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
voice.setValid(false);
|
||||
@@ -151,7 +162,8 @@ public class DMRDataMessageFactory
|
||||
switch(dpf)
|
||||
{
|
||||
case CONFIRMED_DATA_PACKET:
|
||||
ConfirmedDataHeader cdh = new ConfirmedDataHeader(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
ConfirmedDataHeader cdh = new ConfirmedDataHeader(pattern, payload, cach, slotType,
|
||||
timestamp, timeslot);
|
||||
cdh.setValid(valid);
|
||||
return cdh;
|
||||
case PROPRIETARY_DATA_PACKET:
|
||||
@@ -231,7 +243,8 @@ public class DMRDataMessageFactory
|
||||
return dh;
|
||||
}
|
||||
case TLC:
|
||||
DataMessage tlc = new Terminator(pattern, payload, cach, slotType, timestamp, timeslot);
|
||||
LCMessage tlcLinkControl = mLinkControlMessageFactory.createFull(payload, timestamp, timeslot, true);
|
||||
DataMessage tlc = new Terminator(pattern, payload, cach, slotType, timestamp, timeslot, tlcLinkControl);
|
||||
if(payload.getCorrectedBitCount() < 0)
|
||||
{
|
||||
tlc.setValid(false);
|
||||
@@ -285,8 +298,7 @@ public class DMRDataMessageFactory
|
||||
/**
|
||||
* De-scramble, decode and error check a BPTC protected raw message and return a 96-bit error corrected
|
||||
* payload or null.
|
||||
* @param message
|
||||
* @return
|
||||
* @param message containing a payload
|
||||
*/
|
||||
private static CorrectedBinaryMessage getPayload(CorrectedBinaryMessage message)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -69,6 +69,14 @@ public abstract class DataMessage extends DMRBurst
|
||||
return getMessage().getInt(BPTC_RESERVED_BITS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value of the reserved bits. Normally this is 2 for RAS enabled systems.
|
||||
*/
|
||||
public int getRAS()
|
||||
{
|
||||
return getBPTCReservedBits();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the BPTC reserved bits value is anything other than 0.
|
||||
* @return true if non-zero.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -23,8 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
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 io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -45,11 +43,13 @@ public abstract class DataMessageWithLinkControl extends DataMessage
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message
|
||||
*/
|
||||
public DataMessageWithLinkControl(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
|
||||
SlotType slotType, long timestamp, int timeslot)
|
||||
SlotType slotType, long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
mLCMessage = linkControl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,12 +57,6 @@ public abstract class DataMessageWithLinkControl extends DataMessage
|
||||
*/
|
||||
public LCMessage getLCMessage()
|
||||
{
|
||||
if(mLCMessage == null)
|
||||
{
|
||||
boolean isTerminator = getSlotType().getDataType() == DataType.TLC;
|
||||
mLCMessage = LCMessageFactory.createFull(getMessage(), getTimestamp(), getTimeslot(), isTerminator);
|
||||
}
|
||||
|
||||
return mLCMessage;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -258,42 +258,19 @@ public class CapacityMaxAdvantageModeVoiceChannelUpdate extends CSBKMessage impl
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(hasChannel1Timeslot1())
|
||||
{
|
||||
getChannel1TS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel1Timeslot2())
|
||||
{
|
||||
getChannel1TS2().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel2Timeslot1())
|
||||
{
|
||||
getChannel2TS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel2Timeslot2())
|
||||
{
|
||||
getChannel2TS2().apply(timeslotFrequencies);
|
||||
}
|
||||
getChannel1TS1().apply(timeslotFrequencies);
|
||||
getChannel1TS2().apply(timeslotFrequencies);
|
||||
getChannel2TS1().apply(timeslotFrequencies);
|
||||
getChannel2TS2().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
//Since both timeslots share the same LCN, we use just TS1's channel for both channel 1 and channel 2
|
||||
if((hasChannel1Timeslot1() || hasChannel1Timeslot2()) && (hasChannel2Timeslot1() || hasChannel2Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel1TS1().getChannelId(), getChannel2TS1().getChannelId()};
|
||||
}
|
||||
else if((hasChannel1Timeslot1() || hasChannel1Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel1TS1().getChannelId()};
|
||||
}
|
||||
else if((hasChannel2Timeslot1() || hasChannel2Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel2TS1().getChannelId()};
|
||||
}
|
||||
|
||||
return getChannel1TS1().getLogicalChannelNumbers();
|
||||
return new int[]{getChannel1TS1().getChannelNumber(),
|
||||
getChannel1TS2().getChannelNumber(),
|
||||
getChannel2TS1().getChannelNumber(),
|
||||
getChannel2TS2().getChannelNumber()};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -176,36 +176,14 @@ public class CapacityMaxOpenModeVoiceChannelUpdate extends CSBKMessage implement
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(hasTimeslot1())
|
||||
{
|
||||
getChannelTS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasTimeslot2())
|
||||
{
|
||||
getChannelTS2().apply(timeslotFrequencies);
|
||||
}
|
||||
getChannelTS1().apply(timeslotFrequencies);
|
||||
getChannelTS2().apply(timeslotFrequencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
boolean ts1 = hasTimeslot1();
|
||||
boolean ts2 = hasTimeslot2();
|
||||
|
||||
if(ts1 && ts2)
|
||||
{
|
||||
return new int[]{getChannelTS1().getChannelNumber(), getChannelTS2().getChannelNumber()};
|
||||
}
|
||||
else if(ts1)
|
||||
{
|
||||
return new int[]{getChannelTS1().getChannelNumber()};
|
||||
}
|
||||
else if(ts2)
|
||||
{
|
||||
return new int[]{getChannelTS2().getChannelNumber()};
|
||||
}
|
||||
|
||||
return new int[0];
|
||||
return new int[]{getChannelTS1().getChannelNumber(), getChannelTS2().getChannelNumber()};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -42,11 +42,12 @@ public class DataHeader extends HeaderMessage
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message from the payload
|
||||
*/
|
||||
public DataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
|
||||
/**
|
||||
@@ -39,10 +40,11 @@ public class HeaderMessage extends DataMessageWithLinkControl
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message from the payload
|
||||
*/
|
||||
public HeaderMessage(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot)
|
||||
long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, linkControl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
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.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
|
||||
/**
|
||||
@@ -40,24 +39,12 @@ public class PiHeader extends HeaderMessage
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message from payload
|
||||
*/
|
||||
public PiHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
public PiHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the embedded link control message
|
||||
*/
|
||||
public LCMessage getLCMessage()
|
||||
{
|
||||
if(mLCMessage == null)
|
||||
{
|
||||
//Overrides default to ignore opcode and construct an encryption parameters message.
|
||||
mLCMessage = LCMessageFactory.createFullEncryption(getMessage(), getTimestamp(), getTimeslot());
|
||||
}
|
||||
|
||||
return mLCMessage;
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, linkControl);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -20,9 +20,10 @@
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.header;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.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.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
|
||||
/**
|
||||
* Voice Header data message.
|
||||
@@ -38,17 +39,22 @@ public class VoiceHeader extends HeaderMessage
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl payload message
|
||||
*/
|
||||
public VoiceHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot)
|
||||
long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, linkControl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append("RAS:").append(getBPTCReservedBits()).append(" ");
|
||||
}
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
sb.append(" VOICE HEADER");
|
||||
sb.append(" ").append(getLCMessage());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -22,7 +22,8 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.edac.CRCDMR;
|
||||
import io.github.dsheirer.edac.Checksum_5_DMR;
|
||||
import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR;
|
||||
import io.github.dsheirer.edac.RS_12_9_DMR;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||
@@ -63,9 +64,18 @@ import org.slf4j.LoggerFactory;
|
||||
public class LCMessageFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LCMessageFactory.class);
|
||||
private static final ReedSolomon_12_9_4_DMR REED_SOLOMON_12_9_4_DMR = new ReedSolomon_12_9_4_DMR();
|
||||
private static final int TERMINATOR_LINK_CONTROL_CRC_MASK = 0x99;
|
||||
private static final int VOICE_LINK_CONTROL_CRC_MASK = 0x96;
|
||||
private final DMRCrcMaskManager mMaskManager;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param maskManager to check and/or override error detection
|
||||
*/
|
||||
public LCMessageFactory(DMRCrcMaskManager maskManager)
|
||||
{
|
||||
mMaskManager = maskManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a full link control message specifically for the PI_HEADER message type.
|
||||
@@ -74,7 +84,7 @@ public class LCMessageFactory
|
||||
* @param timeslot of the original message
|
||||
* @return encryption parameters link control.
|
||||
*/
|
||||
public static FullLCMessage createFullEncryption(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
public FullLCMessage createFullEncryption(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
return new EncryptionParameters(message, timestamp, timeslot);
|
||||
}
|
||||
@@ -87,28 +97,27 @@ public class LCMessageFactory
|
||||
* @param isTerminator set to true if this is LC for a terminator data burst.
|
||||
* @return message class
|
||||
*/
|
||||
public static FullLCMessage createFull(CorrectedBinaryMessage message, long timestamp, int timeslot, boolean isTerminator)
|
||||
public FullLCMessage createFull(CorrectedBinaryMessage message, long timestamp, int timeslot, boolean isTerminator)
|
||||
{
|
||||
if(message == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Message cannot be null");
|
||||
}
|
||||
|
||||
boolean valid = true;
|
||||
int residual = 0;
|
||||
|
||||
if(message.size() == 77)
|
||||
{
|
||||
valid = Checksum_5_DMR.isValid(message);
|
||||
residual = Checksum_5_DMR.isValid(message);
|
||||
}
|
||||
else if(message.size() == 96)
|
||||
{
|
||||
//RS(12,9,4) can correct up to floor(4/2) = 1 bit error.
|
||||
valid = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message,
|
||||
isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
|
||||
//RS(12,9,4) can correct up to floor((12-9)/2) = 1 symbol (or up to 8 bits if they are in the same symbol).
|
||||
residual = RS_12_9_DMR.correct(message, isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.warn("Unrecognized Link Control Message Size: " + message.size());
|
||||
mLog.warn("Unrecognized Link Control Message Size: {}", message.size());
|
||||
}
|
||||
|
||||
LCOpcode opcode = FullLCMessage.getOpcode(message);
|
||||
@@ -116,13 +125,29 @@ public class LCMessageFactory
|
||||
//Some Hytera Tier-3 systems use a zero-valued mask when the FLC is carried in the voice header or terminator
|
||||
//and in the same transmission it uses the standard mask when carried across the voice frames. It doesn't make
|
||||
//sense, but maybe it's a bug in their software implementation?
|
||||
if(!valid && message.size() == 96 && opcode == LCOpcode.FULL_STANDARD_GROUP_VOICE_CHANNEL_USER)
|
||||
if(residual != 0 && message.size() == 96 && opcode == LCOpcode.FULL_STANDARD_GROUP_VOICE_CHANNEL_USER)
|
||||
{
|
||||
//Retry the RS(12,9,4) with a mask value of zero
|
||||
valid = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message, 0);
|
||||
residual = RS_12_9_DMR.correct(message, 0);
|
||||
}
|
||||
|
||||
FullLCMessage flc = null;
|
||||
boolean valid = (residual != 0);
|
||||
|
||||
if(!valid)
|
||||
{
|
||||
if(message.size() == 96)
|
||||
{
|
||||
//Check if the residual CRC check value is commonly seen for this opcode (ie RAS).
|
||||
valid = mMaskManager.isValidRS12_9(opcode.getValue(), residual, timestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Check if the residual CRC check value is commonly seen for this opcode (ie RAS).
|
||||
valid = mMaskManager.isValidCRC5(opcode.getValue(), residual, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
FullLCMessage flc;
|
||||
|
||||
switch(opcode)
|
||||
{
|
||||
@@ -208,47 +233,24 @@ public class LCMessageFactory
|
||||
{
|
||||
LCOpcode opcode = ShortLCMessage.getOpcode(message);
|
||||
|
||||
ShortLCMessage slc = null;
|
||||
|
||||
switch(opcode)
|
||||
ShortLCMessage slc = switch(opcode)
|
||||
{
|
||||
case SHORT_STANDARD_NULL_MESSAGE:
|
||||
slc = new NullMessage(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_STANDARD_ACTIVITY_UPDATE:;
|
||||
slc = new ActivityUpdateMessage(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION:
|
||||
slc = new CapacityPlusRestChannel(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_CONNECT_PLUS_CONTROL_CHANNEL:
|
||||
slc = new ConnectPlusControlChannel(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL:
|
||||
slc = new ConnectPlusTrafficChannel(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_STANDARD_CONTROL_CHANNEL_SYSTEM_PARAMETERS:
|
||||
slc = new ControlChannelSystemParameters(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS:
|
||||
slc = new TrafficChannelSystemParameters(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_HYTERA_XPT_CHANNEL:
|
||||
case SHORT_STANDARD_XPT_CHANNEL:
|
||||
slc = new HyteraXPTChannel(message, timestamp, timeslot);
|
||||
break;
|
||||
case SHORT_STANDARD_UNKNOWN:
|
||||
default:
|
||||
slc = new UnknownShortLCMessage(message, timestamp, timeslot);
|
||||
break;
|
||||
}
|
||||
|
||||
boolean valid = CRCDMR.crc8(message, 36) == 0;
|
||||
|
||||
// System.out.println("SLC MSG:" + message.toHexString() + " CRC-8 RESIDUAL: " + CRCDMR.crc8(message, 36));
|
||||
//
|
||||
slc.setValid(valid);
|
||||
case SHORT_STANDARD_NULL_MESSAGE -> new NullMessage(message, timestamp, timeslot);
|
||||
case SHORT_STANDARD_ACTIVITY_UPDATE -> new ActivityUpdateMessage(message, timestamp, timeslot);
|
||||
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION ->
|
||||
new CapacityPlusRestChannel(message, timestamp, timeslot);
|
||||
case SHORT_CONNECT_PLUS_CONTROL_CHANNEL -> new ConnectPlusControlChannel(message, timestamp, timeslot);
|
||||
case SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL -> new ConnectPlusTrafficChannel(message, timestamp, timeslot);
|
||||
case SHORT_STANDARD_CONTROL_CHANNEL_SYSTEM_PARAMETERS ->
|
||||
new ControlChannelSystemParameters(message, timestamp, timeslot);
|
||||
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS ->
|
||||
new TrafficChannelSystemParameters(message, timestamp, timeslot);
|
||||
case SHORT_HYTERA_XPT_CHANNEL, SHORT_STANDARD_XPT_CHANNEL ->
|
||||
new HyteraXPTChannel(message, timestamp, timeslot);
|
||||
default -> new UnknownShortLCMessage(message, timestamp, timeslot);
|
||||
};
|
||||
|
||||
slc.setValid(CRCDMR.crc8(message, 36) == 0);
|
||||
return slc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -22,30 +22,30 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.BitSetFullException;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRCrcMaskManager;
|
||||
import io.github.dsheirer.module.decode.dmr.bptc.BPTC_128_77;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Reassembles embedded signalling fragments into Full Link Control Messages
|
||||
*/
|
||||
public class FLCAssembler
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(FLCAssembler.class);
|
||||
private CorrectedBinaryMessage mAssemblingMessage;
|
||||
private int mFragmentCount = 0;
|
||||
private long mTimestamp;
|
||||
private int mTimeslot;
|
||||
private BPTC_128_77 mBPTC = new BPTC_128_77();
|
||||
private final int mTimeslot;
|
||||
private final BPTC_128_77 mBPTC = new BPTC_128_77();
|
||||
private final LCMessageFactory mMessageFactory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public FLCAssembler(int timeslot)
|
||||
public FLCAssembler(int timeslot, DMRCrcMaskManager maskManager)
|
||||
{
|
||||
mTimeslot = timeslot;
|
||||
mMessageFactory = new LCMessageFactory(maskManager);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,8 +111,7 @@ public class FLCAssembler
|
||||
|
||||
if(mFragmentCount == 4)
|
||||
{
|
||||
// message = decode(mAssemblingMessage, mTimestamp, mTimeslot);
|
||||
message = decode2(mAssemblingMessage, mTimestamp, mTimeslot);
|
||||
message = decode(mAssemblingMessage, mTimestamp, mTimeslot);
|
||||
}
|
||||
|
||||
mAssemblingMessage = null;
|
||||
@@ -123,12 +122,12 @@ public class FLCAssembler
|
||||
/**
|
||||
* Decodes an assembled FLC message, performs error detection and correction and returns a constructed FLC message
|
||||
*/
|
||||
private FullLCMessage decode2(BinaryMessage message, long timestamp, int timeslot)
|
||||
private FullLCMessage decode(BinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
if(message != null)
|
||||
{
|
||||
CorrectedBinaryMessage extractedMessage = mBPTC.extract(message);
|
||||
FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false);
|
||||
FullLCMessage flco = mMessageFactory.createFull(extractedMessage, timestamp, timeslot, false);
|
||||
|
||||
if(extractedMessage.getCorrectedBitCount() < 0)
|
||||
{
|
||||
@@ -141,111 +140,6 @@ public class FLCAssembler
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * Decodes an assembled FLC message, performs error detection and correction and returns a constructed FLC message
|
||||
// */
|
||||
// private static FullLCMessage decode(BinaryMessage message, long timestamp, int timeslot)
|
||||
// {
|
||||
// if(message != null)
|
||||
// {
|
||||
// System.out.println("FLC Raw:" + message);
|
||||
// boolean valid = true;
|
||||
//
|
||||
// CorrectedBinaryMessage descrambled = new CorrectedBinaryMessage(128);
|
||||
// int src;
|
||||
// descrambled.set(127, message.get(127));
|
||||
// for(int i = 0; i < 127; i++)
|
||||
// {
|
||||
// src = (i * 8) % 127;
|
||||
// boolean x = message.get(src);
|
||||
// descrambled.set(i, message.get(src));
|
||||
// }
|
||||
//
|
||||
// System.out.println("FLC Des:" + descrambled);
|
||||
// int errorCount = 0;
|
||||
//
|
||||
// for(int row = 0; row < 8; row++)
|
||||
// {
|
||||
// int errorIndex = Hamming16.getErrorIndex(descrambled, row * 16);
|
||||
// BinaryMessage rowErrors = BPTC_128_77.getRowErrors(descrambled);
|
||||
// BinaryMessage columnErrors = BPTC_128_77.getColumnErrors(descrambled);
|
||||
//
|
||||
// int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16);
|
||||
//
|
||||
// if(rowErrorCount == 0)
|
||||
// {
|
||||
// System.out.println("Row [" + row + "]");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// System.out.println("Row [" + row + "] Error Count: " + rowErrorCount + " Index:" + errorIndex +
|
||||
// " Value: " + message.get(errorIndex) +
|
||||
// " Row Errors: " + rowErrors + " Column Errors: " + columnErrors );
|
||||
// }
|
||||
//
|
||||
// errorCount += rowErrorCount;
|
||||
//
|
||||
// if(errorCount > 1)
|
||||
// {
|
||||
// valid = false;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //If valid, check the column parity values
|
||||
// if(valid)
|
||||
// {
|
||||
// //Check the column parity values
|
||||
// for(int column = 0; column < 16; column++)
|
||||
// {
|
||||
// boolean parity = descrambled.get(column);
|
||||
//
|
||||
// for(int y = 1; y < 8; y++)
|
||||
// {
|
||||
// parity ^= descrambled.get(column + (y * 16));
|
||||
// }
|
||||
//
|
||||
// if(parity)
|
||||
// {
|
||||
// valid = false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //Extract the message. Row 1 and 2 are 11 bits. Rows 3-7 are 10 bits.
|
||||
// CorrectedBinaryMessage extractedMessage = new CorrectedBinaryMessage(77);
|
||||
// int pointer = 0;
|
||||
//
|
||||
// for(int row = 0; row < 2; row++)
|
||||
// {
|
||||
// for(int column = 0; column < 11; column++)
|
||||
// {
|
||||
// extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for(int row = 2; row < 7; row++)
|
||||
// {
|
||||
// for(int column = 0; column < 10; column++)
|
||||
// {
|
||||
// extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
|
||||
// }
|
||||
//
|
||||
// //Transfer the checksum parity bit from this row
|
||||
// extractedMessage.set(70 + row, descrambled.get(row * 16 + 10));
|
||||
// }
|
||||
//
|
||||
// extractedMessage.setCorrectedBitCount(errorCount);
|
||||
//
|
||||
//
|
||||
// FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false);
|
||||
// flco.setValid(valid);
|
||||
// return flco;
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Adds the cach payload to the currently assembling message
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2025 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
|
||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.sync.DMRSyncPattern;
|
||||
|
||||
/**
|
||||
@@ -39,10 +40,11 @@ public class Terminator extends DataMessageWithLinkControl
|
||||
* @param slotType for this data message
|
||||
* @param timestamp message was received
|
||||
* @param timeslot for the DMR burst
|
||||
* @param linkControl message from the payload
|
||||
*/
|
||||
public Terminator(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType,
|
||||
long timestamp, int timeslot)
|
||||
long timestamp, int timeslot, LCMessage linkControl)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot, linkControl);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user