diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java index 16b204962..54db953de 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageFramer.java @@ -146,6 +146,15 @@ private void dispatchBufferA() mBufferATimeslot = cach.getTimeslot(); mBufferBTimeslot = (mBufferATimeslot == 1 ? 2 : 1); } + else if(mBufferAPattern.isMobileStationSyncPattern()) + { + mBufferATimeslot = 1; + mBufferBTimeslot = 2; + if(mBufferBPattern == DMRSyncPattern.UNKNOWN) + { + mBufferBPattern = DMRSyncPattern.DIRECT_EMPTY_TIMESLOT; + } + } dispatch(DMRMessageFactory.create(mBufferAPattern, message, cach, getTimestamp(), mBufferATimeslot)); @@ -156,8 +165,8 @@ private void dispatchBufferA() mBufferAPattern = DMRSyncPattern.getNextVoice(mBufferAPattern); } - //Automatically trigger buffer B burst collection if it is collecting a voice super frame. - if(mBufferBPattern.isVoicePattern()) + //Automatically trigger buffer B burst collection if it is collecting a voice super frame or mobile direct. + if(mBufferBPattern.isVoicePattern() || mBufferBPattern == DMRSyncPattern.DIRECT_EMPTY_TIMESLOT) { mAssemblingBurst = true; mBufferAActive = false; @@ -205,6 +214,16 @@ private void dispatchBufferB() mBufferBTimeslot = cach.getTimeslot(); mBufferATimeslot = (mBufferBTimeslot == 1 ? 2 : 1); } + else if(mBufferBPattern.isMobileStationSyncPattern()) + { + mBufferBTimeslot = 1; + mBufferATimeslot = 2; + + if(mBufferAPattern == DMRSyncPattern.UNKNOWN) + { + mBufferAPattern = DMRSyncPattern.DIRECT_EMPTY_TIMESLOT; + } + } dispatch(DMRMessageFactory.create(mBufferBPattern, burst, cach, getTimestamp(), mBufferBTimeslot)); @@ -215,8 +234,8 @@ private void dispatchBufferB() mBufferBPattern = DMRSyncPattern.getNextVoice(mBufferBPattern); } - //Automatically trigger buffer A burst collection if it is collecting a voice super frame. - if(mBufferAPattern.isVoicePattern()) + //Automatically trigger buffer A burst collection if it is collecting a voice super frame or mobile direct. + if(mBufferAPattern.isVoicePattern() || mBufferAPattern == DMRSyncPattern.DIRECT_EMPTY_TIMESLOT) { mAssemblingBurst = true; mBufferAActive = true; diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java index fd111a7dc..3cf4625fb 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTCBase.java @@ -49,6 +49,18 @@ public BPTCBase(IHamming hamming, int columnCount, int rowCount) mRowCount = rowCount; } + /** + * Used by the correct() method to employ an optional attempt to correct multiple row, two-bit errors where each + * set of row errors don't shadow each other based on a test where the column cardinality is twice the row + * cardinality. This is probably not mathematically correct and should only be enabled when the decoded message + * payload has a good CRC check after the extraction (ie. only use for FLC). + * @return true if enabled, false (default) otherwise + */ + protected boolean canCorrectMultiRow2BitErrors() + { + return false; + } + /** * Performs error detection and correction. * @param message to correct @@ -100,6 +112,28 @@ public boolean correct(CorrectedBinaryMessage message) } } + //3: fix single row, multi-column errors + if(rows.cardinality() == 1 && !columns.isEmpty()) + { + List solution = correctMultiBitErrors(rows.nextSetBit(0), message, columns, rows); + + if(!solution.isEmpty()) + { + correctedIndexes.addAll(solution); + } + } + + //4: fix one or more rows that each have 2 bit errors, not shadowing each other. + if(canCorrectMultiRow2BitErrors() && rows.cardinality() > 0 && columns.cardinality() / rows.cardinality() == 2) + { + List solution = correctMultipleRowTwoBitErrorsNotShadowing(columns, rows, message); + + if(!solution.isEmpty()) + { + correctedIndexes.addAll(solution); + } + } + if(columns.isEmpty() && rows.isEmpty()) { message.incrementCorrectedBitCount(correctedIndexes.size()); @@ -204,6 +238,90 @@ public BinaryMessage getColumnErrors(CorrectedBinaryMessage message) return columns; } + /** + * Calculates the indices that make up the intersections of the row and column errors. + * @param rows bitmap indicating rows with errors + * @param columns bitmap indicating columns with errors + * @return set of intersection indices. + */ + public List getIntersectionIndices(BinaryMessage columns, BinaryMessage rows) + { + List intersections = new ArrayList<>(); + + for(int row = rows.nextSetBit(0); row >= 0 && row < mRowCount; row = rows.nextSetBit(row + 1)) + { + for(int column = columns.nextSetBit(0); column >= 0 && column < mColumnCount; column = columns.nextSetBit(column + 1)) + { + intersections.add(getIndex(column, row)); + } + } + + return intersections; + } + + /** + * Calculates the indices that make up the intersections of the row and column errors. + * @param rows bitmap indicating rows with errors + * @param columns bitmap indicating columns with errors + * @return set of intersection indices. + */ + public List getIntersectionIndices(BinaryMessage columns, int row) + { + List intersections = new ArrayList<>(); + + for(int column = columns.nextSetBit(0); column >= 0 && column < mColumnCount; column = columns.nextSetBit(column + 1)) + { + intersections.add(getIndex(column, row)); + } + + return intersections; + } + + /** + * Correct multiple rows with two-bit errors each where the error columns are not shadowing each other. + * @param columns error map + * @param rows error map + * @param message to correct + * @return corrected indices + */ + public List correctMultipleRowTwoBitErrorsNotShadowing(BinaryMessage columns, BinaryMessage rows, + CorrectedBinaryMessage message) + { + List solution = new ArrayList<>(); + + for(int row = rows.nextSetBit(0); row >= 0 && row < mRowCount; row = rows.nextSetBit(row + 1)) + { + for(int column1 = columns.nextSetBit(0); column1 >= 0; column1 = columns.nextSetBit(column1 + 1)) + { + for(int column2 = columns.nextSetBit(column1 + 1); column2 >= 0; column2 = columns.nextSetBit(column2 + 1)) + { + int index1 = getIndex(column1, row); + int index2 = getIndex(column2, row); + message.flip(index1); + message.flip(index2); + + if(isRowCorrect(row, message)) + { + solution.add(index1); + solution.add(index2); + columns.clear(column1); + columns.clear(column2); + rows.clear(row); + column1 = mColumnCount; + column2 = mColumnCount; + } + else + { + message.flip(index1); + message.flip(index2); + } + } + } + } + + return solution; + } + /** * Corrects single bit errors in each row using only the Hamming error index. * @param row to correct @@ -313,7 +431,18 @@ public void logErrorMap(CorrectedBinaryMessage message) int offset = row * mColumnCount; sb.append("Row ").append(row).append(": "); sb.append(message.getSubMessage(offset, offset + mColumnCount)); - sb.append(" ").append(offset).append(":").append(offset + mColumnCount).append("\n"); + sb.append(" (").append(offset).append(":").append(offset + mColumnCount).append(")"); + + for(int error = offset; error < offset + mColumnCount; error++) + { + if(message.get(error)) + { + sb.append(" ").append(error); + } + } + + sb.append("\n"); + } System.out.println(sb); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java index 274a70c08..ff0931996 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/bptc/BPTC_128_77.java @@ -34,6 +34,15 @@ public BPTC_128_77() super(new Hamming16(), 16, 8); } + /** + * Allow the base class to attempt to correct multiple rows of 2-bit errors each + */ + @Override + protected boolean canCorrectMultiRow2BitErrors() + { + return true; + } + /** * Performs error detection and correction and extracts the payload from the BPTC encoded message. * @param message with BPTC encoding. @@ -93,15 +102,15 @@ public CorrectedBinaryMessage deinterleave(BinaryMessage interleaved) public static void main(String[] args) { BPTC_128_77 bptc = new BPTC_128_77(); - String deinterleavedRaw = "00000000000000000000000000000000001000000010010101011001001000010000000000000000000000000110101000010110111100010000111110011111"; + String deinterleavedRaw = "00000100000101010000001000111000100000000001001101000010001001000000000010101000111100000000001000011000100010110010100100100000"; + CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedRaw)); - String deinterleavedReference = "00000000000000000000000000000000000000000010011100011001001000110000000000000000000000000110101000010110111100010000111110011111"; //No errors + String deinterleavedReference = "00000100000101010000011000111001100000000001001101000010001101100000000110101000111100000000101000011000100010110010100100100000"; //No errors CorrectedBinaryMessage deinterleavedReferenceMessage = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedReference)); - String interleaved = "00000000000000000000000000010010000100010000001000000010000100010000001100000110001101100000001100000101001000010010010100110011"; //Under test - CorrectedBinaryMessage interleavedMessage = new CorrectedBinaryMessage(BinaryMessage.load(interleaved)); +// String interleaved = "00000000000000000000000000010010000100010000001000000010000100010000001100000110001101100000001100000101001000010010010100110011"; //Under test +// CorrectedBinaryMessage interleavedMessage = new CorrectedBinaryMessage(BinaryMessage.load(interleaved)); // CorrectedBinaryMessage deinterleaved = deinterleave(interleavedMessage); - CorrectedBinaryMessage deinterleaved = new CorrectedBinaryMessage(BinaryMessage.load(deinterleavedRaw)); String deinterleavedUncorrected = deinterleaved.toString(); @@ -120,6 +129,7 @@ public static void main(String[] args) System.out.println("--------------------------------"); System.out.println("Residual Error Map:"); bptc.logErrorMap(deinterleaved); + System.out.println("Columns:" + bptc.getColumnErrors(deinterleaved) + " Rows:" + bptc.getRowErrors(deinterleaved)); System.out.println("--------------------------------"); } else diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java index 2705248da..c9921c82e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/data/lc/full/FLCAssembler.java @@ -129,7 +129,12 @@ private FullLCMessage decode2(BinaryMessage message, long timestamp, int timeslo { CorrectedBinaryMessage extractedMessage = mBPTC.extract(message); FullLCMessage flco = LCMessageFactory.createFull(extractedMessage, timestamp, timeslot, false); - flco.setValid(extractedMessage.getCorrectedBitCount() >= 0); + + if(extractedMessage.getCorrectedBitCount() < 0) + { + flco.setValid(false); + } + return flco; }