diff --git a/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java b/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java index a3646217..9fe86daf 100644 --- a/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java +++ b/src/main/java/io/github/dsheirer/gui/viewer/DmrViewer.java @@ -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 @@ -19,12 +19,18 @@ package io.github.dsheirer.gui.viewer; -import io.github.dsheirer.message.IMessage; +import com.google.common.eventbus.EventBus; +import io.github.dsheirer.channel.state.DecoderStateEvent; +import io.github.dsheirer.controller.channel.Channel; +import io.github.dsheirer.module.decode.dmr.DMRDecoderState; import io.github.dsheirer.module.decode.dmr.DMRHardSymbolProcessor; import io.github.dsheirer.module.decode.dmr.DMRMessageFramer; import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor; +import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager; import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR; +import io.github.dsheirer.module.decode.p25.phase1.message.P25P1Message; import io.github.dsheirer.record.binary.BinaryReader; +import io.github.dsheirer.sample.Broadcaster; import io.github.dsheirer.util.ThreadPool; import java.io.File; import java.nio.ByteBuffer; @@ -77,9 +83,9 @@ public class DmrViewer extends VBox private Preferences mPreferences = Preferences.userNodeForPackage(DmrViewer.class); private Button mSelectFileButton; private Label mSelectedFileLabel; - private TableView mMessageTableView; - private ObservableList mMessages = FXCollections.observableArrayList(); - private FilteredList mFilteredMessages = new FilteredList<>(mMessages); + private TableView mMessagePackageTableView; + private ObservableList mMessagePackages = FXCollections.observableArrayList(); + private FilteredList mFilteredMessagePackages = new FilteredList<>(mMessagePackages); private CheckBox mShowTS0; private CheckBox mShowTS1; private CheckBox mShowTS2; @@ -89,6 +95,7 @@ public class DmrViewer extends VBox private Button mFindButton; private Button mFindNextButton; private ProgressIndicator mLoadingIndicator; + private MessagePackageViewer mMessagePackageViewer; public DmrViewer() { @@ -99,9 +106,10 @@ public class DmrViewer extends VBox fileBox.setMaxWidth(Double.MAX_VALUE); fileBox.setAlignment(Pos.CENTER_LEFT); fileBox.setSpacing(5); + getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER); + HBox.setHgrow(getSelectFileButton(), Priority.NEVER); HBox.setHgrow(getSelectedFileLabel(), Priority.ALWAYS); - getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER); HBox compressedBox = new HBox(); compressedBox.setAlignment(Pos.CENTER_RIGHT); @@ -130,9 +138,10 @@ public class DmrViewer extends VBox VBox.setVgrow(fileBox, Priority.NEVER); VBox.setVgrow(filterBox, Priority.NEVER); - VBox.setVgrow(getMessageTableView(), Priority.ALWAYS); + VBox.setVgrow(getMessagePackageTableView(), Priority.ALWAYS); + VBox.setVgrow(getMessagePackageViewer(), Priority.NEVER); - getChildren().addAll(fileBox, filterBox, getMessageTableView()); + getChildren().addAll(fileBox, filterBox, getMessagePackageTableView(), getMessagePackageViewer()); } /** @@ -158,7 +167,7 @@ public class DmrViewer extends VBox { if(file != null && file.exists()) { - mMessages.clear(); + mMessagePackages.clear(); getLoadingIndicator().setVisible(true); getSelectedFileLabel().setText("Loading ..."); final boolean useCompressed = getUseCompressedTalkgroups().isSelected(); @@ -168,7 +177,7 @@ public class DmrViewer extends VBox @Override public void run() { - List messages = new ArrayList<>(); + List messagePackages = new ArrayList<>(); DMRMessageFramer messageFramer = new DMRMessageFramer(); messageFramer.start(); DMRHardSymbolProcessor symbolProcessor = new DMRHardSymbolProcessor(messageFramer); @@ -176,7 +185,48 @@ public class DmrViewer extends VBox config.setUseCompressedTalkgroups(useCompressed); DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config); messageFramer.setListener(messageProcessor); - messageProcessor.setMessageListener(message -> messages.add(message)); + MessagePackager messagePackager = new MessagePackager(); + + //Setup a temporary event bus to capture channel start processing requests + EventBus eventBus = new EventBus("debug"); + eventBus.register(messagePackager); + Channel empty = new Channel("Empty"); + empty.setDecodeConfiguration(new DecodeConfigDMR()); + DMRTrafficChannelManager trafficChannelManager = new DMRTrafficChannelManager(empty); + trafficChannelManager.setInterModuleEventBus(eventBus); + + //Register to receive events + trafficChannelManager.addDecodeEventListener(messagePackager::add); + DMRDecoderState decoderState1 = new DMRDecoderState(empty, 1, trafficChannelManager); + Broadcaster decoderStateEventBroadcaster1 = new Broadcaster<>(); + decoderState1.setDecoderStateListener(decoderStateEventBroadcaster1); + decoderStateEventBroadcaster1.addListener(messagePackager::add); + DMRDecoderState decoderState2 = new DMRDecoderState(empty, 2, trafficChannelManager); + decoderState1.addDecodeEventListener(messagePackager::add); + decoderState1.start(); + + Broadcaster decoderStateEventBroadcaster2 = new Broadcaster<>(); + decoderState2.setDecoderStateListener(decoderStateEventBroadcaster2); + decoderStateEventBroadcaster2.addListener(messagePackager::add); + decoderState2.addDecodeEventListener(messagePackager::add); + decoderState2.start(); + + messageProcessor.setMessageListener(message -> { + //Add the initial message to the packager so that it can be combined with any decoder state events. + messagePackager.add(message); + if(message.getTimeslot() == P25P1Message.TIMESLOT_1) + { + decoderState1.receive(message); + } + else if(message.getTimeslot() == P25P1Message.TIMESLOT_2) + { + decoderState2.receive(message); + } + + //Collect the packaged message with events + messagePackages.add(messagePackager.getMessageWithEvents()); + }); + try(BinaryReader reader = new BinaryReader(file.toPath(), 200)) { @@ -194,8 +244,8 @@ public class DmrViewer extends VBox Platform.runLater(() -> { getLoadingIndicator().setVisible(false); getSelectedFileLabel().setText(file.getName()); - mMessages.addAll(messages); - getMessageTableView().scrollTo(0); + mMessagePackages.addAll(messagePackages); + getMessagePackageTableView().scrollTo(0); }); } }); @@ -207,7 +257,7 @@ public class DmrViewer extends VBox */ private void updateFilters() { - Predicate timeslotPredicate = message -> + Predicate timeslotPredicate = message -> (getShowTS0().isSelected() && (message.getTimeslot() == 0)) || (getShowTS1().isSelected() && (message.getTimeslot() == 1)) || (getShowTS2().isSelected() && (message.getTimeslot() == 2)); @@ -216,12 +266,12 @@ public class DmrViewer extends VBox if(filterText == null || filterText.isEmpty()) { - mFilteredMessages.setPredicate(timeslotPredicate); + mFilteredMessagePackages.setPredicate(timeslotPredicate); } else { - Predicate textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase()); - mFilteredMessages.setPredicate(timeslotPredicate.and(textPredicate)); + Predicate textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase()); + mFilteredMessagePackages.setPredicate(timeslotPredicate.and(textPredicate)); } } @@ -233,12 +283,12 @@ public class DmrViewer extends VBox { if(text != null && !text.isEmpty()) { - for(IMessage message: mFilteredMessages) + for(MessagePackage messagePackage: mFilteredMessagePackages) { - if(message.toString().toLowerCase().contains(text.toLowerCase())) + if(messagePackage.toString().toLowerCase().contains(text.toLowerCase())) { - getMessageTableView().getSelectionModel().select(message); - getMessageTableView().scrollTo(message); + getMessagePackageTableView().getSelectionModel().select(messagePackage); + getMessagePackageTableView().scrollTo(messagePackage); return; } } @@ -253,7 +303,7 @@ public class DmrViewer extends VBox { if(text != null && !text.isEmpty()) { - IMessage selected = getMessageTableView().getSelectionModel().getSelectedItem(); + MessagePackage selected = getMessagePackageTableView().getSelectionModel().getSelectedItem(); if(selected == null) { @@ -261,18 +311,18 @@ public class DmrViewer extends VBox return; } - int row = mFilteredMessages.indexOf(selected); + int row = mFilteredMessagePackages.indexOf(selected); - for(int x = row + 1; x < mFilteredMessages.size(); x++) + for(int x = row + 1; x < mFilteredMessagePackages.size(); x++) { - if(x < mFilteredMessages.size()) + if(x < mFilteredMessagePackages.size()) { - IMessage message = mFilteredMessages.get(x); + MessagePackage messagePackage = mFilteredMessagePackages.get(x); - if(message.toString().toLowerCase().contains(text.toLowerCase())) + if(messagePackage.toString().toLowerCase().contains(text.toLowerCase())) { - getMessageTableView().getSelectionModel().select(message); - getMessageTableView().scrollTo(message); + getMessagePackageTableView().getSelectionModel().select(messagePackage); + getMessagePackageTableView().scrollTo(messagePackage); return; } } @@ -280,26 +330,41 @@ public class DmrViewer extends VBox } } - /** - * List view control with DMR messages - */ - private TableView getMessageTableView() + private MessagePackageViewer getMessagePackageViewer() { - if(mMessageTableView == null) + if(mMessagePackageViewer == null) { - mMessageTableView = new TableView<>(); - mMessageTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - mMessageTableView.setPlaceholder(getLoadingIndicator()); - SortedList sortedList = new SortedList<>(mFilteredMessages); - sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty()); - mMessageTableView.setItems(sortedList); + mMessagePackageViewer = new MessagePackageViewer(); + mMessagePackageViewer.setMaxWidth(Double.MAX_VALUE); - mMessageTableView.setOnKeyPressed(event -> + //Register for table selection events to display the selected value. + getMessagePackageTableView().getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> getMessagePackageViewer().set(newValue)); + } + + return mMessagePackageViewer; + } + + /** + * List view control with DMR message packages + */ + private TableView getMessagePackageTableView() + { + if(mMessagePackageTableView == null) + { + mMessagePackageTableView = new TableView<>(); + mMessagePackageTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + mMessagePackageTableView.setPlaceholder(getLoadingIndicator()); + SortedList sortedList = new SortedList<>(mFilteredMessagePackages); + sortedList.comparatorProperty().bind(mMessagePackageTableView.comparatorProperty()); + mMessagePackageTableView.setItems(sortedList); + + mMessagePackageTableView.setOnKeyPressed(event -> { if(KEY_CODE_COPY.match(event)) { final Set rows = new TreeSet<>(); - for (final TablePosition tablePosition : mMessageTableView.getSelectionModel().getSelectedCells()) + for (final TablePosition tablePosition : mMessagePackageTableView.getSelectionModel().getSelectedCells()) { rows.add(tablePosition.getRow()); } @@ -319,7 +384,7 @@ public class DmrViewer extends VBox boolean firstCol = true; - for (final TableColumn column : mMessageTableView.getColumns()) + for (final TableColumn column : mMessagePackageTableView.getColumns()) { if(firstCol) { @@ -360,18 +425,34 @@ public class DmrViewer extends VBox messageColumn.setText("Message"); messageColumn.setCellValueFactory((Callback) param -> { SimpleStringProperty property = new SimpleStringProperty(); - if(param.getValue() instanceof IMessage message) + if(param.getValue() instanceof MessagePackage messagePackage) { - property.set(message.toString()); + property.set(messagePackage.getMessage().toString()); } return property; }); - mMessageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn); + TableColumn decodeEventCountColumn = new TableColumn(); + decodeEventCountColumn.setPrefWidth(50); + decodeEventCountColumn.setText("Events"); + decodeEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decodeEventCount")); + + TableColumn decoderStateEventCountColumn = new TableColumn(); + decoderStateEventCountColumn.setPrefWidth(50); + decoderStateEventCountColumn.setText("States"); + decoderStateEventCountColumn.setCellValueFactory(new PropertyValueFactory<>("decoderStateEventCount")); + + TableColumn channelStartCountColumn = new TableColumn(); + channelStartCountColumn.setPrefWidth(50); + channelStartCountColumn.setText("Starts"); + channelStartCountColumn.setCellValueFactory(new PropertyValueFactory<>("channelStartProcessingRequestCount")); + + mMessagePackageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn, + decodeEventCountColumn, decoderStateEventCountColumn, channelStartCountColumn); } - return mMessageTableView; + return mMessagePackageTableView; } /** 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 862983be..2f6db237 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 @@ -37,6 +37,7 @@ import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier; import io.github.dsheirer.identifier.integer.IntegerIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.log.LoggingSuppressor; +import io.github.dsheirer.message.EmptyTimeslotPlaceholderMessage; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.TimeslotMessage; import io.github.dsheirer.module.decode.DecoderType; @@ -44,7 +45,6 @@ import io.github.dsheirer.module.decode.dmr.channel.DMRAbsoluteChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; import io.github.dsheirer.module.decode.dmr.channel.DMRLsn; import io.github.dsheirer.module.decode.dmr.event.DMRDecodeEvent; -import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup; import io.github.dsheirer.module.decode.dmr.message.DMRMessage; import io.github.dsheirer.module.decode.dmr.message.data.DataMessage; import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage; @@ -316,7 +316,11 @@ public class DMRDecoderState extends TimeslotDecoderState { processSMS(sms); } - else if(message instanceof DMRMessage) + else if(isValid(message) && message instanceof DMRMessage) + { + broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot())); + } + else if(message instanceof EmptyTimeslotPlaceholderMessage) { broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot())); } @@ -555,9 +559,6 @@ public class DMRDecoderState extends TimeslotDecoderState { updateCurrentCall(DecodeEventType.CALL, "REPEATER", message.getTimestamp()); } - - //Use the timeslot as the talkgroup identifier since DCDM & simple repeater modes don't use talkgroups - getIdentifierCollection().update(DMRTalkgroup.create(getTimeslot())); } else { @@ -1292,10 +1293,8 @@ public class DMRDecoderState extends TimeslotDecoderState } break; case FULL_STANDARD_GROUP_VOICE_CHANNEL_USER: - if(message instanceof GroupVoiceChannelUser) + if(message instanceof GroupVoiceChannelUser gvcu) { - GroupVoiceChannelUser gvcu = (GroupVoiceChannelUser)message; - if(isTerminator) { closeCurrentCallEvent(message.getTimestamp());