diff --git a/src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java index a2bcdd3f6..c3a4952c7 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java @@ -69,9 +69,17 @@ public MenuBar getMenuBar() Menu createNewViewerMenu = new Menu("New Viewer ..."); MenuItem dmrMenuItem = new MenuItem("DMR"); - dmrMenuItem.onActionProperty().set(event -> getTabPane().getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer()))); + dmrMenuItem.onActionProperty().set(event -> { + Tab tab = new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer()); + getTabPane().getTabs().add(tab); + getTabPane().getSelectionModel().select(tab); + }); MenuItem p25p1MenuItem = new MenuItem("P25P1"); - p25p1MenuItem.onActionProperty().set(event -> getTabPane().getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()))); + p25p1MenuItem.onActionProperty().set(event -> { + Tab tab = new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()); + getTabPane().getTabs().add(tab); + getTabPane().getSelectionModel().select(tab); + }); createNewViewerMenu.getItems().addAll(dmrMenuItem, p25p1MenuItem); MenuItem exitMenu = new MenuItem("Exit"); diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java index 7da5ff1ef..bd7e516c0 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRBurstFramer.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2020 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -284,11 +284,27 @@ private void dispatch() } 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 + //When we don't have sufficient timeslot tracking data, we either have misaligned timeslots from a + //repeater or we have some form of mobile burst. If mobile, process to the correct timeslto, otherwise + //dump the messages to timeslot 0 so that the burst doesn't corrupt the decoder states for either + // timeslot, until we positively regain timeslot tracking if(burst1 != null) { - mBurstDetectListener.burstDetected(burst1, mSyncTrackerTimeslot1.getSyncPattern(), 0); + DMRSyncPattern sync1 = mSyncTrackerTimeslot1.getSyncPattern(); + + if(sync1.isMobileStationSyncPattern() || sync1.isDirectModeTS1()) + { + mBurstDetectListener.burstDetected(burst1, sync1, 1); + } + else if(sync1.isDirectModeTS2()) + { + mBurstDetectListener.burstDetected(burst1, sync1, 2); + } + else + { + mBurstDetectListener.burstDetected(burst1, sync1, 0); + } + mDibitCounter -= BURST_DIBIT_LENGTH; } else @@ -298,7 +314,21 @@ private void dispatch() if(burst2 != null) { - mBurstDetectListener.burstDetected(burst2, mSyncTrackerTimeslot2.getSyncPattern(), 0); + DMRSyncPattern sync2 = mSyncTrackerTimeslot2.getSyncPattern(); + + if(sync2.isMobileStationSyncPattern() || sync2.isDirectModeTS1()) + { + mBurstDetectListener.burstDetected(burst2, sync2, 1); + } + else if(sync2.isDirectModeTS2()) + { + mBurstDetectListener.burstDetected(burst2, sync2, 2); + } + else + { + mBurstDetectListener.burstDetected(burst2, sync2, 0); + } + mDibitCounter -= BURST_DIBIT_LENGTH; } else diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java index 2994c3544..26062362b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRDecoderState.java @@ -445,11 +445,18 @@ else if(packet.getPacket() instanceof XCMPPacket xcmp) */ private void processVoice(VoiceMessage message) { - if(message.getSyncPattern().isDirectMode()) + if(message.getSyncPattern().isMobileSyncPattern()) { - updateCurrentCall(DecodeEventType.CALL, "DIRECT MODE", message.getTimestamp()); + if(message.getSyncPattern().isDirectMode()) + { + updateCurrentCall(DecodeEventType.CALL, "DIRECT MODE", message.getTimestamp()); + } + else + { + updateCurrentCall(DecodeEventType.CALL, "REPEATER", message.getTimestamp()); + } - //Use the timeslot as the talkgroup identifier since DCDM mode doesn't use talkgroups + //Use the timeslot as the talkgroup identifier since DCDM & simple repeater modes don't use talkgroups getIdentifierCollection().update(DMRTalkgroup.create(getTimeslot())); } else diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java index c3a6f4597..9f42028de 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRMessageProcessor.java @@ -153,10 +153,8 @@ else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid()) dispatch(message); //Extract the Full Link Control message fragment from the Voice with embedded signalling message - if(message instanceof VoiceEMBMessage) + if(message instanceof VoiceEMBMessage voice) { - VoiceEMBMessage voice = (VoiceEMBMessage)message; - if(message.getTimeslot() == 1) { FullLCMessage flco = mFLCAssemblerTimeslot1.process(voice.getEMB().getLCSS(), diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java index 79a7ff10f..cc7d4e6b9 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRSyncPattern.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -91,9 +91,17 @@ public enum DMRSyncPattern private static final EnumSet 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 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); + //Direct Mode TS 1 Sync Patterns + private static final EnumSet DIRECT_MODE_TS1_PATTERNS = EnumSet.of(DIRECT_MODE_VOICE_TIMESLOT_1, + DIRECT_MODE_DATA_TIMESLOT_1); + + //Direct Mode TS 2 Sync Patterns + private static final EnumSet DIRECT_MODE_TS2_PATTERNS = EnumSet.of(DIRECT_MODE_VOICE_TIMESLOT_2, + DIRECT_MODE_DATA_TIMESLOT_2); + + //Mobile Station Sync Patterns + private static final EnumSet MOBILE_STATION_PATTERNS = EnumSet.of(MOBILE_STATION_DATA, + MOBILE_STATION_VOICE, MS_VOICE_FRAME_B, MS_VOICE_FRAME_C, MS_VOICE_FRAME_D, MS_VOICE_FRAME_E, MS_VOICE_FRAME_F); /** * Pattern that represents the enum entry @@ -140,7 +148,43 @@ public boolean hasCACH() */ public boolean isDirectMode() { - return DIRECT_MODE_PATTERNS.contains(this); + return isDirectModeTS1() || isDirectModeTS2(); + } + + /** + * Indicates if this is Direct Mode sync pattern for timestot 1 + * @return true if DMTS1 + */ + public boolean isDirectModeTS1() + { + return DIRECT_MODE_TS1_PATTERNS.contains(this); + } + + /** + * Indicates if this is Direct Mode sync pattern for timestot 2 + * @return true if DMTS2 + */ + public boolean isDirectModeTS2() + { + return DIRECT_MODE_TS2_PATTERNS.contains(this); + } + + /** + * Indicates if this is a mobile station sync pattern + * @return true if a mobile station sync pattern. + */ + public boolean isMobileStationSyncPattern() + { + return MOBILE_STATION_PATTERNS.contains(this); + } + + /** + * Indicates if this is a mobile sync pattern, either mobile station or direct mode. + * @return true if a mobile sync pattern. + */ + public boolean isMobileSyncPattern() + { + return isMobileStationSyncPattern() || isDirectMode(); } /** diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java index 9a6fbdb6a..9300db1fd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceAMessage.java @@ -48,15 +48,13 @@ public String toString() { StringBuilder sb = new StringBuilder(); - if(getSyncPattern().isDirectMode()) + if(!getSyncPattern().isMobileSyncPattern()) { - sb.append(getSyncPattern()); - } - else - { - sb.append("CC:- ").append(getSyncPattern()); + sb.append("CC:- "); } + sb.append(getSyncPattern()); + return sb.toString(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java index 4e83e99e1..fb026978f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/message/voice/VoiceEMBMessage.java @@ -56,7 +56,7 @@ public String toString() { StringBuilder sb = new StringBuilder(); - if(getEMB().isValid()) + if(!getSyncPattern().isMobileSyncPattern() && getEMB().isValid()) { sb.append("CC:").append(getEMB().getColorCode()).append(" "); }