From d246ffff30f9bbbcc08e37a7d5e0c14d2a76c852 Mon Sep 17 00:00:00 2001 From: Christopher Varner Date: Thu, 11 Nov 2021 13:13:13 -0500 Subject: [PATCH] Adds event type as a filtering option in the Events tab --- .../channel/metadata/NowPlayingPanel.java | 17 +- .../module/decode/dmr/DMRDecoderState.java | 166 ++---- .../decode/dmr/DMRTrafficChannelManager.java | 9 +- .../dmr/event/DMRChannelGrantEvent.java | 14 +- .../module/decode/event/DecodeEvent.java | 5 + .../module/decode/event/DecodeEventType.java | 4 +- .../decode/event/PlottableDecodeEvent.java | 11 + .../filter/DecodedCommandEventFilter.java | 21 +- .../filter/lastheard/LastHeardFilterSet.java | 17 + .../filter/lastheard/LastHeardHistory.java | 77 +++ .../lastheard/LastHeardHistoryRequest.java | 35 ++ .../lastheard/LastHeardHistoryResponse.java | 47 ++ .../filter/lastheard/LastHeardIconFilter.java | 97 ++++ .../filter/lastheard/LastHeardModel.java | 292 +++++++++++ .../filter/lastheard/LastHeardPanel.java | 482 ++++++++++++++++++ .../filter/lastseen/LastSeenFilterSet.java | 17 + .../filter/lastseen/LastSeenHistory.java | 77 +++ .../lastseen/LastSeenHistoryRequest.java | 35 ++ .../lastseen/LastSeenHistoryResponse.java | 47 ++ .../filter/lastseen/LastSeenIconFilter.java | 96 ++++ .../event/filter/lastseen/LastSeenModel.java | 289 +++++++++++ .../event/filter/lastseen/LastSeenPanel.java | 482 ++++++++++++++++++ .../fleetsync2/Fleetsync2DecoderState.java | 74 ++- .../decode/lj1200/LJ1200DecoderState.java | 3 + .../decode/ltrnet/LTRNetDecodeEvent.java | 49 ++ .../decode/ltrnet/LTRNetDecoderState.java | 91 ++-- .../ltrstandard/LTRStandardDecodeEvent.java | 49 ++ .../ltrstandard/LTRStandardDecoderState.java | 5 +- .../module/decode/mdc1200/MDCDecodeEvent.java | 49 ++ .../decode/mdc1200/MDCDecoderState.java | 23 +- .../mpt1327/MPT1327ChannelGrantEvent.java | 12 + .../decode/mpt1327/MPT1327DecodeEvent.java | 49 ++ .../decode/mpt1327/MPT1327DecoderState.java | 115 ++--- .../mpt1327/MPT1327TrafficChannelManager.java | 2 + .../module/decode/p25/P25DecodeEvent.java | 2 +- .../decode/p25/phase2/P25P2DecoderState.java | 212 +++----- .../decode/passport/PassportDecodeEvent.java | 49 ++ .../decode/passport/PassportDecoderState.java | 10 +- .../decode/tait/Tait1200DecoderState.java | 7 + 39 files changed, 2686 insertions(+), 452 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardFilterSet.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistory.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistoryRequest.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistoryResponse.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardIconFilter.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardModel.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardPanel.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenFilterSet.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistory.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistoryRequest.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistoryResponse.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenIconFilter.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenModel.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenPanel.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecodeEvent.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecodeEvent.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecodeEvent.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecodeEvent.java create mode 100644 src/main/java/io/github/dsheirer/module/decode/passport/PassportDecodeEvent.java diff --git a/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java b/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java index 7108c0e7..8126e97b 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java @@ -25,19 +25,24 @@ import io.github.dsheirer.gui.power.ChannelPowerPanel; import io.github.dsheirer.icon.IconModel; import io.github.dsheirer.module.decode.event.DecodeEventPanel; import io.github.dsheirer.module.decode.event.MessageActivityPanel; +import io.github.dsheirer.module.decode.event.filter.lastheard.LastHeardPanel; +import io.github.dsheirer.module.decode.event.filter.lastseen.LastSeenPanel; import io.github.dsheirer.playlist.PlaylistManager; import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.settings.SettingsManager; import java.awt.Color; import net.miginfocom.swing.MigLayout; -import javax.swing.JPanel; +import javax.swing.*; +import java.awt.*; public class NowPlayingPanel extends JPanel { private ChannelMetadataPanel mChannelMetadataPanel; private ChannelDetailPanel mChannelDetailPanel; private DecodeEventPanel mDecodeEventPanel; + private LastSeenPanel mLastSeenPanel; + private LastHeardPanel mLastHeardPanel; private MessageActivityPanel mMessageActivityPanel; private ChannelPowerPanel mChannelPowerPanel; @@ -50,6 +55,8 @@ public class NowPlayingPanel extends JPanel { mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager()); mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel()); + mLastSeenPanel = new LastSeenPanel(iconModel, userPreferences, playlistManager.getAliasModel()); + mLastHeardPanel = new LastHeardPanel(iconModel, userPreferences, playlistManager.getAliasModel()); mMessageActivityPanel = new MessageActivityPanel(userPreferences); mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences); mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager); @@ -64,15 +71,13 @@ public class NowPlayingPanel extends JPanel JideTabbedPane tabbedPane = new JideTabbedPane(); tabbedPane.addTab("Details", mChannelDetailPanel); tabbedPane.addTab("Events", mDecodeEventPanel); + tabbedPane.addTab("Last Seen", mLastSeenPanel); + tabbedPane.addTab("Last Heard", mLastHeardPanel); tabbedPane.addTab("Messages", mMessageActivityPanel); tabbedPane.addTab("Channel", mChannelPowerPanel); tabbedPane.setFont(this.getFont()); tabbedPane.setForeground(Color.BLACK); - //Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing - tabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(tabbedPane.getSelectedIndex() == tabbedPane - .indexOfComponent(mChannelPowerPanel))); - JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT); splitPane.setShowGripper(true); splitPane.add(mChannelMetadataPanel); @@ -81,6 +86,8 @@ public class NowPlayingPanel extends JPanel mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelDetailPanel); mChannelMetadataPanel.addProcessingChainSelectionListener(mDecodeEventPanel); + mChannelMetadataPanel.addProcessingChainSelectionListener(mLastSeenPanel); + mChannelMetadataPanel.addProcessingChainSelectionListener(mLastHeardPanel); mChannelMetadataPanel.addProcessingChainSelectionListener(mMessageActivityPanel); mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelPowerPanel); } 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 57b4fe2c..e28b4e7f 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 @@ -314,6 +314,7 @@ public class DMRDecoderState extends TimeslotDecoderState DecodeEvent smsEvent = DMRDecodeEvent.builder(packet.getTimestamp()) .eventDescription(DecodeEventType.SMS.name()) + .eventType(DecodeEventType.DATA_PACKET) .identifiers(mic) .timeslot(packet.getTimeslot()) .details("SMS:" + hyteraSmsPacket.getSMS()) @@ -327,6 +328,7 @@ public class DMRDecoderState extends TimeslotDecoderState DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(packet.getTimestamp()) .eventDescription(DecodeEventType.UNKNOWN_PACKET.name()) + .eventType(DecodeEventType.DATA_PACKET) .identifiers(mic) .timeslot(packet.getTimeslot()) .details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString()) @@ -337,6 +339,7 @@ public class DMRDecoderState extends TimeslotDecoderState { DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp()) .eventDescription(DecodeEventType.DATA_PACKET.name()) + .eventType(DecodeEventType.DATA_PACKET) .identifiers(getMergedIdentifierCollection(packet.getIdentifiers())) .timeslot(packet.getTimeslot()) .details(packet.toString()) @@ -505,14 +508,8 @@ public class DMRDecoderState extends TimeslotDecoderState case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD: if(csbk instanceof Acknowledge) { - DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(((Acknowledge)csbk).getReason().toString()) - .build(); - - broadcast(ackEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, + ((Acknowledge) csbk).getReason().toString())); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot())); break; @@ -520,14 +517,8 @@ public class DMRDecoderState extends TimeslotDecoderState case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC: if(csbk instanceof Acknowledge) { - DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(((Acknowledge)csbk).getReason().toString()) - .build(); - - broadcast(ackEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, + ((Acknowledge) csbk).getReason().toString())); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot())); break; @@ -537,33 +528,16 @@ public class DMRDecoderState extends TimeslotDecoderState switch(((Ahoy)csbk).getServiceKind()) { case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE: - DecodeEvent registerEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(DecodeEventType.REGISTER.toString()) - .build(); - broadcast(registerEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, DecodeEventType.REGISTER.toString())); break; case CANCEL_CALL_SERVICE: - DecodeEvent cancelEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("CANCEL CALL") - .build(); - broadcast(cancelEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, "CANCEL CALL")); break; case SUPPLEMENTARY_SERVICE: if(csbk instanceof StunReviveKill) { - DecodeEvent stunEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(((StunReviveKill)csbk).getCommand() + " RADIO") - .build(); - broadcast(stunEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, + ((StunReviveKill)csbk).getCommand() + " RADIO")); } break; case FULL_DUPLEX_MS_TO_MS_PACKET_CALL_SERVICE: @@ -577,15 +551,9 @@ public class DMRDecoderState extends TimeslotDecoderState if(csbk instanceof ServiceRadioCheck) { ServiceRadioCheck src = (ServiceRadioCheck)csbk; - - DecodeEvent checkEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RADIO_CHECK.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details(src.getServiceDescription() + " SERVICE FOR " + - (src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO")) - .build(); - broadcast(checkEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.RADIO_CHECK, + src.getServiceDescription() + " SERVICE FOR " + + (src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO"))); } break; } @@ -599,14 +567,7 @@ public class DMRDecoderState extends TimeslotDecoderState if(aloha.hasRadioIdentifier()) { - DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("Aloha Acknowledge") - .build(); - - broadcast(ackEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge")); resetState(); } } @@ -618,24 +579,13 @@ public class DMRDecoderState extends TimeslotDecoderState switch(((Announcement)csbk).getAnnouncementType()) { case MASS_REGISTRATION: - DecodeEvent massEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.REGISTER.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("MASS REGISTRATION") - .build(); - broadcast(massEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.REGISTER, "MASS REGISTRATION")); break; case VOTE_NOW_ADVICE: if(csbk instanceof VoteNowAdvice) { - DecodeEvent voteEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode()) - .build(); - broadcast(voteEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, + "VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode())); } break; } @@ -653,13 +603,8 @@ public class DMRDecoderState extends TimeslotDecoderState case STANDARD_PROTECT: if(csbk instanceof Protect) { - DecodeEvent protectEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("PROTECT: " + ((Protect)csbk).getProtectKind()) - .build(); - broadcast(protectEvent); + broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, + "PROTECT: " + ((Protect)csbk).getProtectKind())); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot())); break; @@ -714,13 +659,7 @@ public class DMRDecoderState extends TimeslotDecoderState if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers())) { - event = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.DATA_CALL.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event); } else @@ -753,13 +692,7 @@ public class DMRDecoderState extends TimeslotDecoderState if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers())) { - event = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.CALL_GROUP.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event); } else @@ -791,13 +724,7 @@ public class DMRDecoderState extends TimeslotDecoderState if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers())) { - event = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.CALL_UNIT_TO_UNIT.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event); } else @@ -817,15 +744,7 @@ public class DMRDecoderState extends TimeslotDecoderState if(cmAloha.hasRadioIdentifier()) { - DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) - .timeslot(csbk.getTimeslot()) - .details("Aloha Acknowledge") - .build(); - - broadcast(ackEvent); - + broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge")); resetState(); } } @@ -850,13 +769,7 @@ public class DMRDecoderState extends TimeslotDecoderState if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers())) { - event = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.DATA_CALL.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event); } else @@ -909,13 +822,7 @@ public class DMRDecoderState extends TimeslotDecoderState if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers())) { - detectedEvent = DMRDecodeEvent.builder(csbk.getTimestamp()) - .channel(channel) - .details(csbk.getOpcode().getLabel()) - .eventDescription(DecodeEventType.CALL_GROUP.toString()) - .identifiers(mergedIdentifiers) - .timeslot(channel.getTimeslot()) - .build(); + detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers); mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent); } else @@ -945,6 +852,27 @@ public class DMRDecoderState extends TimeslotDecoderState } } + private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel, IdentifierCollection mergedIdentifiers) { + return DMRDecodeEvent.builder(csbk.getTimestamp()) + .channel(channel) + .eventType(decodeEventType) + .details(csbk.getOpcode().getLabel()) + .eventDescription(decodeEventType.toString()) + .identifiers(mergedIdentifiers) + .timeslot(channel.getTimeslot()) + .build(); + } + + private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) { + return DMRDecodeEvent.builder(csbk.getTimestamp()) + .eventType(decodeEventType) + .eventDescription(decodeEventType.toString()) + .identifiers(getMergedIdentifierCollection(csbk.getIdentifiers())) + .timeslot(csbk.getTimeslot()) + .details(details) + .build(); + } + /** * Creates a copy of the current identifier collection, removes any USER class identifiers and loads the identifiers * argument values into the collection. diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java index ec6577f8..7d322ea3 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/DMRTrafficChannelManager.java @@ -325,12 +325,14 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I int lsn = channel.getLogicalSlotNumber(); DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn); + DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted); if(isStale(event, timestamp, identifierCollection)) //Create new event { event = DMRChannelGrantEvent.channelGrantBuilder(timestamp) .channel(channel) - .eventDescription(getEventType(opcode, identifierCollection, encrypted).toString()) + .eventType(decodeEventType) + .eventDescription(decodeEventType.toString()) .details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : "")) .identifiers(identifierCollection) .build(); @@ -351,7 +353,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I event = DMRChannelGrantEvent.channelGrantBuilder(timestamp) .channel(channel) - .eventDescription(getEventType(opcode, identifierCollection, encrypted).toString() + " - Continue") + .eventType(decodeEventType) + .eventDescription(decodeEventType.toString() + " - Continue") .details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : "")) .identifiers(identifierCollection) .build(); @@ -391,7 +394,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I { if(event.getEventDescription() == null) { - event.setEventDescription(getEventType(opcode, identifierCollection, encrypted) + IGNORED); + event.setEventDescription(decodeEventType + IGNORED); } else if(!event.getEventDescription().endsWith(IGNORED)) { diff --git a/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRChannelGrantEvent.java b/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRChannelGrantEvent.java index 5d1d1e77..94bf729a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRChannelGrantEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/dmr/event/DMRChannelGrantEvent.java @@ -21,6 +21,7 @@ package io.github.dsheirer.module.decode.dmr.event; import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.module.decode.dmr.channel.DMRChannel; +import io.github.dsheirer.module.decode.event.DecodeEventType; /** * DMR Channel Grant Event. @@ -74,6 +75,7 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent { protected long mTimeStart; protected long mDuration; + protected DecodeEventType mDecodeEventType; protected String mEventDescription; protected IdentifierCollection mIdentifierCollection; protected DMRChannel mChannel; @@ -121,7 +123,7 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent /** * Sets the channel descriptor for this event - * @param channelDescriptor + * @param channel */ public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel) { @@ -129,6 +131,15 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent return this; } + /** + * Sets the Decode Event type for this event. + * @param eventType + */ + public DMRChannelGrantDecodeEventBuilder eventType(DecodeEventType eventType) { + mDecodeEventType = eventType; + return this; + } + /** * Sets the event description text * @param description of the event @@ -170,6 +181,7 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent decodeEvent.setTimeslot(mChannel.getTimeslot()); decodeEvent.setDetails(mDetails); decodeEvent.setDuration(mDuration); + decodeEvent.setEventType(mDecodeEventType); decodeEvent.setEventDescription(mEventDescription); decodeEvent.setIdentifierCollection(mIdentifierCollection); return decodeEvent; diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java index ae4fa9ed..130d879b 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEvent.java @@ -313,6 +313,10 @@ public class DecodeEvent implements IDecodeEvent return this; } + /** + * Sets the Decode Event type for this event. + * @param eventType + */ public DecodeEventBuilder eventType(DecodeEventType eventType) { mDecodeEventType = eventType; return this; @@ -374,6 +378,7 @@ public class DecodeEvent implements IDecodeEvent decodeEvent.setChannelDescriptor(mChannelDescriptor); decodeEvent.setDetails(mDetails); decodeEvent.setDuration(mDuration); + decodeEvent.setEventType(mDecodeEventType); decodeEvent.setEventDescription(mEventDescription); decodeEvent.setIdentifierCollection(mIdentifierCollection); decodeEvent.setProtocol(mProtocol); diff --git a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java index a7b1f47e..3eb72bbd 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/DecodeEventType.java @@ -14,7 +14,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * **************************************************************************** + * ***************************************************************************** */ package io.github.dsheirer.module.decode.event; @@ -23,6 +23,7 @@ public enum DecodeEventType { AFFILIATE("Affiliate"), ANNOUNCEMENT("Announcement"), + ACKNOWLEDGE("Acknowledge"), AUTOMATIC_REGISTRATION_SERVICE("Motorola ARS"), CALL("Call"), CALL_ENCRYPTED("Encrypted Call"), @@ -32,6 +33,7 @@ public enum DecodeEventType CALL_PATCH_GROUP_ENCRYPTED("Encrypted Patch Call"), CALL_ALERT("Call Alert"), CALL_DETECT("Call Detect"), + CALL_IN_PROGRESS("Call In Progress"), CALL_DO_NOT_MONITOR("Call-Do Not Monitor"), CALL_END("Call End"), CALL_INTERCONNECT("Telephone Call"), diff --git a/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java index 87fca123..99a1faec 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/PlottableDecodeEvent.java @@ -103,6 +103,7 @@ public class PlottableDecodeEvent extends DecodeEvent private long mTimeStart; private long mDuration; private String mEventDescription; + private DecodeEventType mDecodeEventType; private IdentifierCollection mIdentifierCollection; private IChannelDescriptor mChannelDescriptor; private String mDetails; @@ -190,6 +191,15 @@ public class PlottableDecodeEvent extends DecodeEvent return this; } + /** + * Sets the Decode Event type for this event. + * @param eventType + */ + public PlottableDecodeEventBuilder eventType(DecodeEventType eventType) { + mDecodeEventType = eventType; + return this; + } + /** * Sets the location for the plottable event */ @@ -226,6 +236,7 @@ public class PlottableDecodeEvent extends DecodeEvent decodeEvent.setChannelDescriptor(mChannelDescriptor); decodeEvent.setDetails(mDetails); decodeEvent.setDuration(mDuration); + decodeEvent.setEventType(mDecodeEventType); decodeEvent.setEventDescription(mEventDescription); decodeEvent.setIdentifierCollection(mIdentifierCollection); decodeEvent.setProtocol(mProtocol); diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCommandEventFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCommandEventFilter.java index 8eb385e2..9c36fe28 100644 --- a/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCommandEventFilter.java +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/DecodedCommandEventFilter.java @@ -1,7 +1,25 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2022 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + package io.github.dsheirer.module.decode.event.filter; import io.github.dsheirer.module.decode.event.DecodeEventType; - import java.util.Arrays; public class DecodedCommandEventFilter extends EventFilter @@ -11,6 +29,7 @@ public class DecodedCommandEventFilter extends EventFilter super("Commands", Arrays.asList( DecodeEventType.ANNOUNCEMENT, DecodeEventType.STATION_ID, + DecodeEventType.ACKNOWLEDGE, DecodeEventType.PAGE, DecodeEventType.QUERY, DecodeEventType.RADIO_CHECK, diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardFilterSet.java new file mode 100644 index 00000000..b03135e2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardFilterSet.java @@ -0,0 +1,17 @@ +package io.github.dsheirer.module.decode.event.filter.lastheard; + +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.module.decode.event.IDecodeEvent; + +public class LastHeardFilterSet extends FilterSet +{ + + public LastHeardFilterSet(AliasModel aliasModel, IconModel iconModel) + { + super("Last Heard To Channel"); + + addFilter(new LastHeardIconFilter("Icon", aliasModel, iconModel)); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistory.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistory.java new file mode 100644 index 00000000..d2f37c7c --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistory.java @@ -0,0 +1,77 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.event.filter.lastheard; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.module.HistoryModule; +import io.github.dsheirer.module.decode.event.DecodeEventHistoryPreloadData; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventListener; +import io.github.dsheirer.sample.Listener; + +/** + * Decode event history module. Maintains a history of decode events and constrains the total history size. + */ +public class LastHeardHistory extends HistoryModule implements IDecodeEventListener +{ + /** + * Constructs an instance + */ + public LastHeardHistory(int historySize) + { + super(historySize); + } + + /** + * Implements the IDecodeEventListener interface - delegates to receive(event) method. + */ + @Override + public Listener getDecodeEventListener() + { + return this; + } + + /** + * Process preload data + */ + @Subscribe + public void process(DecodeEventHistoryPreloadData preloadData) + { + for(IDecodeEvent decodeEvent: preloadData.getData()) + { + receive(decodeEvent); + } + } + + /** + * Processes a request for decode event history and posts the response back + * to the processing chain event bus so that any of the modules can receive that history. + * + * Note: this is principally used by the DMR decoder for Capacity+ REST channel rotation to transfer the decode + * event history from a traffic channel conversion to the new rest channel. + * + * @param request for decode event history + */ + @Subscribe + public void process(LastHeardHistoryRequest request) + { + getInterModuleEventBus().post(new LastHeardHistoryResponse(this)); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistoryRequest.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistoryRequest.java new file mode 100644 index 00000000..ac1ad974 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistoryRequest.java @@ -0,0 +1,35 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.event.filter.lastheard; + +import io.github.dsheirer.module.ModuleEventBusMessage; + +/** + * Request for decode event history from a processing chain for the specified channel. + */ +public class LastHeardHistoryRequest extends ModuleEventBusMessage +{ + /** + * Constructs an instance + */ + public LastHeardHistoryRequest() + { + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistoryResponse.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistoryResponse.java new file mode 100644 index 00000000..cfedfdbd --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardHistoryResponse.java @@ -0,0 +1,47 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.event.filter.lastheard; + +import io.github.dsheirer.module.ModuleEventBusMessage; + +/** + * Response to decode event history request from a processing chain for the specified channel. + */ +public class LastHeardHistoryResponse extends ModuleEventBusMessage +{ + private LastHeardHistory mLastHeardHistory; + + /** + * Constructs an instance + * @param history that has a decode event history + */ + public LastHeardHistoryResponse(LastHeardHistory history) + { + mLastHeardHistory = history; + } + + /** + * Decode event history module + */ + public LastHeardHistory getDecodeEventHistory() + { + return mLastHeardHistory; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardIconFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardIconFilter.java new file mode 100644 index 00000000..37b66203 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardIconFilter.java @@ -0,0 +1,97 @@ +package io.github.dsheirer.module.decode.event.filter.lastheard; + +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.filter.Filter; +import io.github.dsheirer.filter.FilterElement; +import io.github.dsheirer.icon.Icon; +import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import javafx.collections.ObservableList; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class LastHeardIconFilter extends Filter +{ + private AliasModel mAliasModel; + private Map> mElements = new HashMap<>(); + + public LastHeardIconFilter(String name, AliasModel aliasModel, IconModel iconModel) + { + super(name); + this.mAliasModel = aliasModel; + + ObservableList icons = iconModel.iconsProperty(); + for (Icon icon : icons) { + mElements.put(icon.getName(), new FilterElement<>(icon.getName())); + } + } + + @Override + public boolean passes(IDecodeEvent iDecodeEvent) + { + if (mEnabled && canProcess(iDecodeEvent)) + { + return matchAliasToFilter(iDecodeEvent) + .map(FilterElement::isEnabled) + .orElse(false); + } + return false; + } + + public Optional> matchAliasToFilter(IDecodeEvent iDecodeEvent) + { + IdentifierCollection identifierCollection = iDecodeEvent.getIdentifierCollection(); + List identifiers = identifierCollection.getIdentifiers(Role.TO); + if (iDecodeEvent.getDuration() > 0 && identifiers != null && !identifiers.isEmpty()) + { + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); + if (aliasList != null) + { + for(Identifier identifier: identifiers) + { + if (identifier.getForm() == Form.TALKGROUP) + { + List aliases = aliasList.getAliases(identifier); + if (!aliases.isEmpty()) + { + for (Alias alias : aliases) { + if (alias.getIconName() == null) + { + // Return any to match aliases with no icon defined. + return Optional.of(mElements.get(IconModel.DEFAULT_ICON)); + } + else + { + return Optional.of(mElements.get(alias.getIconName())); + } + } + } + } + } + } + } + return Optional.empty(); + } + + @Override + public boolean canProcess(IDecodeEvent iDecodeEvent) + { + return true; + } + + @Override + public List> getFilterElements() + { + return new ArrayList<>(mElements.values()); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardModel.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardModel.java new file mode 100644 index 00000000..f0a7e8d8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardModel.java @@ -0,0 +1,292 @@ +/* + * + * * ****************************************************************************** + * * Copyright (C) 2014-2019 Dennis Sheirer + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see + * * ***************************************************************************** + * + * + */ +package io.github.dsheirer.module.decode.event.filter.lastheard; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.filter.EventFilterProvider; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.sample.Listener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class LastHeardModel extends AbstractTableModel implements Listener, EventFilterProvider +{ + private static final long serialVersionUID = 1L; + private final static Logger mLog = LoggerFactory.getLogger(LastHeardModel.class); + + public static final int COLUMN_TIME = 0; + public static final int COLUMN_DURATION = 1; + public static final int COLUMN_EVENT = 2; + public static final int COLUMN_FROM_ID = 3; + public static final int COLUMN_FROM_ALIAS = 4; + public static final int COLUMN_TO_ID = 5; + public static final int COLUMN_TO_ALIAS = 6; + public static final int COLUMN_CHANNEL = 7; + public static final int COLUMN_FREQUENCY = 8; + public static final int COLUMN_DETAILS = 9; + + protected int mMaxMessages = 500; + + protected List mEvents = new ArrayList<>(); + protected FilterSet mLastSeenFilterSet; + + protected String[] mHeaders = new String[]{"Time", "Duration", "Event", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Details"}; + + public LastHeardModel(AliasModel aliasModel, IconModel iconModel) + { + mLastSeenFilterSet = new LastHeardFilterSet(aliasModel, iconModel); + MyEventBus.getGlobalEventBus().register(this); + } + + /** + * Receives preference update notifications via the event bus + * @param preferenceType that was updated + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.DECODE_EVENT) + { + for(int row = 0; row < mEvents.size(); row++) + { + fireTableCellUpdated(row, COLUMN_TIME); + } + } + else if(preferenceType == PreferenceType.TALKGROUP_FORMAT) + { + for(int row = 0; row < mEvents.size(); row++) + { + fireTableCellUpdated(row, COLUMN_FROM_ID); + fireTableCellUpdated(row, COLUMN_TO_ID); + } + } + } + + /** + * Access the complete list of events managed by this model. + */ + public List getEvents() + { + return new ArrayList<>(mEvents); + } + + public void dispose() + { + MyEventBus.getGlobalEventBus().unregister(this); + Iterator it = mEvents.iterator(); + + while(it.hasNext()) + { + it.remove(); + } + } + + public void clear() + { + mEvents.clear(); + fireTableDataChanged(); + } + + /** + * Clears all events from this model and loads the events argument + */ + public void clearAndSet(List events) + { + mEvents.clear(); + mEvents.addAll(events); + fireTableDataChanged(); + } + + public void reset() + { + dispose(); + fireTableDataChanged(); + } + + public int getMaxMessageCount() + { + return mMaxMessages; + } + + public void setMaxMessageCount(int count) + { + mMaxMessages = count; + } + + /** + * Adds, updates or deletes the event from the model. Producers can send + * the same call event multiple times to indicate that information in the + * event is updated. Producers can also mark the event as invalid and the + * event will be removed from the model. + */ + public void receive(final IDecodeEvent event) + { + if (mLastSeenFilterSet != null && !mLastSeenFilterSet.passes(event)) + { + return; + } + if(!mEvents.contains(event)) + { + mEvents.add(0, event); + fireTableRowsInserted(0, 0); + prune(); + } + else + { + int row = mEvents.indexOf(event); + fireTableRowsUpdated(row, row); + } + } + + private void prune() + { + while(mEvents.size() > mMaxMessages) + { + int index = mEvents.size() - 1; + mEvents.remove(index); + fireTableRowsDeleted(index, index); + } + } + + @Override + public FilterSet getFilterSet() { + return mLastSeenFilterSet; + } + + @Override + public void setFilterSet(FilterSet filterSet) { + this.mLastSeenFilterSet = filterSet; + } + + @Override + public int getRowCount() + { + return mEvents.size(); + } + + @Override + public int getColumnCount() + { + return mHeaders.length; + } + + public String getColumnName(int column) + { + return mHeaders[column]; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) + { + synchronized(mEvents) + { + IDecodeEvent event = mEvents.get(rowIndex); + + if(event != null) + { + switch(columnIndex) + { + case COLUMN_TIME: + return event.getTimeStart(); + case COLUMN_DURATION: + return event.getDuration(); + case COLUMN_EVENT: + return event.getEventDescription(); + case COLUMN_FROM_ID: + return event.getIdentifierCollection(); + case COLUMN_FROM_ALIAS: + return event.getIdentifierCollection(); + case COLUMN_TO_ID: + return event.getIdentifierCollection(); + case COLUMN_TO_ALIAS: + return event.getIdentifierCollection(); + case COLUMN_CHANNEL: + IChannelDescriptor channelDescriptor = event.getChannelDescriptor(); + + if(channelDescriptor != null) + { + if(event.hasTimeslot()) + { + return channelDescriptor.toString() + " TS:" + event.getTimeslot(); + } + else + { + return channelDescriptor.toString(); + } + } + else + { + if(event.hasTimeslot()) + { + return "TS:" + event.getTimeslot(); + } + else + { + return null; + } + } + case COLUMN_FREQUENCY: + return event.getChannelDescriptor(); + case COLUMN_DETAILS: + return event.getDetails(); + } + } + } + + return null; + } + + @Override + public Class getColumnClass(int columnIndex) + { + switch(columnIndex) + { + case COLUMN_DURATION: + case COLUMN_TIME: + return Long.class; + case COLUMN_DETAILS: + case COLUMN_EVENT: + return String.class; + case COLUMN_FREQUENCY: + case COLUMN_FROM_ALIAS: + case COLUMN_FROM_ID: + case COLUMN_TO_ALIAS: + case COLUMN_TO_ID: + return IdentifierCollection.class; + case COLUMN_CHANNEL: + return String.class; + } + return super.getColumnClass(columnIndex); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardPanel.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardPanel.java new file mode 100644 index 00000000..ff043fab --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastheard/LastHeardPanel.java @@ -0,0 +1,482 @@ +/* + * + * * ****************************************************************************** + * * Copyright (C) 2014-2019 Dennis Sheirer + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see + * * ***************************************************************************** + * + * + */ +package io.github.dsheirer.module.decode.event.filter.lastheard; + +import com.google.common.base.Joiner; +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.filter.AllPassFilter; +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.module.ProcessingChain; +import io.github.dsheirer.module.decode.event.DecodeEventHistory; +import io.github.dsheirer.module.decode.event.DecodeEventModel; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.filter.EventClearButton; +import io.github.dsheirer.module.decode.event.filter.EventClearHandler; +import io.github.dsheirer.module.decode.event.filter.EventFilterButton; +import io.github.dsheirer.module.decode.event.filter.EventFilterProvider; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor; +import io.github.dsheirer.sample.Listener; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.*; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +public class LastHeardPanel extends JPanel implements Listener +{ + private static final long serialVersionUID = 1L; + private final static Logger mLog = LoggerFactory.getLogger(LastHeardPanel.class); + private static final String TABLE_PREFERENCE_KEY = "decode.lastseen.panel"; + + private JTable mTable; + private JTableColumnWidthMonitor mTableColumnWidthMonitor; + private LastHeardModel mLastHeardModel; + private DecodeEventHistory mCurrentEventHistory; + private JScrollPane mEmptyScroller; + private IconModel mIconModel; + private AliasModel mAliasModel; + private UserPreferences mUserPreferences; + private TimestampCellRenderer mTimestampCellRenderer; + private LastSeenManagementPanel mLastSeenManagementPanel; + + /** + * View for call event table + * @param iconModel to display alias icons in table rows + */ + public LastHeardPanel(IconModel iconModel, UserPreferences userPreferences, AliasModel aliasModel) + { + MyEventBus.getGlobalEventBus().register(this); + + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + mIconModel = iconModel; + mAliasModel = aliasModel; + mLastHeardModel = new LastHeardModel(aliasModel, iconModel); + mUserPreferences = userPreferences; + mTimestampCellRenderer = new TimestampCellRenderer(); + mTable = new JTable(mLastHeardModel); + mTable.setAutoCreateRowSorter(true); + mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + mLastSeenManagementPanel = new LastSeenManagementPanel(); + mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY); + updateCellRenderers(); + + add(mLastSeenManagementPanel, "span,growx"); + + mEmptyScroller = new JScrollPane(mTable); + add(mEmptyScroller); + } + + public void dispose() + { + MyEventBus.getGlobalEventBus().unregister(this); + } + + /** + * Receives preference update notifications via the event bus + * @param preferenceType that was updated + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.TALKGROUP_FORMAT) + { + EventQueue.invokeLater(() -> mTimestampCellRenderer.updatePreferences()); + } + } + + private void updateCellRenderers() + { + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TIME) + .setCellRenderer(mTimestampCellRenderer); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_DURATION) + .setCellRenderer(new DurationCellRenderer()); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ID) + .setCellRenderer(new IdentifierCellRenderer(Role.FROM)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ALIAS) + .setCellRenderer(new AliasedIdentifierCellRenderer(Role.FROM)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ID) + .setCellRenderer(new IdentifierCellRenderer(Role.TO)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ALIAS) + .setCellRenderer(new AliasedIdentifierCellRenderer(Role.TO)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_CHANNEL) + .setCellRenderer(new ChannelDescriptorCellRenderer()); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FREQUENCY) + .setCellRenderer(new FrequencyCellRenderer()); + } + + @Override + public void receive(final ProcessingChain processingChain) + { + if(mCurrentEventHistory != null) + { + mCurrentEventHistory.removeListener(mLastHeardModel); + } + + EventQueue.invokeLater(() -> { + if(processingChain != null) + { + mCurrentEventHistory = processingChain.getDecodeEventHistory(); + mLastHeardModel.clearAndSet(mCurrentEventHistory.getItems()); + processingChain.getDecodeEventHistory().addListener(mLastHeardModel); + mLastSeenManagementPanel.enableButtons(); + } + else + { + mCurrentEventHistory = null; + mLastHeardModel.clearAndSet(Collections.emptyList()); + mLastSeenManagementPanel.disableButtons(); + } + }); + } + + public class LastSeenManagementPanel extends JPanel + { + private static final long serialVersionUID = 1L; + + private EventFilterButton mFilterButton; + private EventClearButton mEventClearButton; + + public LastSeenManagementPanel() + { + setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", "")); + + initializeFilterButton(); + initializeClearButton(); + disableButtons(); + + add(mFilterButton); + add(mEventClearButton); + } + + public void enableButtons() + { + mFilterButton.setEnabled(true); + mEventClearButton.setEnabled(true); + } + + public void disableButtons() + { + mFilterButton.setEnabled(false); + mEventClearButton.setEnabled(false); + } + + private void initializeFilterButton() + { + EventFilterProvider filterProvider = (EventFilterProvider) mTable.getModel(); + FilterSet filterSet = new FilterSet<>(new AllPassFilter<>()); + if (filterProvider != null) { + filterSet = filterProvider.getFilterSet(); + } + mFilterButton = new EventFilterButton<>("Last Heard To Channel Filter Editor", filterSet); + } + + private void initializeClearButton() { + mEventClearButton = new EventClearButton( + ((LastHeardModel) mTable.getModel()).getMaxMessageCount() + ); + mEventClearButton.setEventClearHandler(new EventClearHandler() { + @Override + public void onHistoryLimitChanged(int newHistoryLimit) { + ((LastHeardModel) mTable.getModel()).setMaxMessageCount(newHistoryLimit); + } + + @Override + public void onClearHistoryClicked() { + ((LastHeardModel) mTable.getModel()).clear(); + } + }); + } + } + + /** + * Custom cell renderer for displaying identifiers from an identifier collection + */ + public class IdentifierCellRenderer extends DefaultTableCellRenderer + { + protected Role mRole; + + /** + * Constructs an instance of the cell renderer. + * + * @param role of the identifier + */ + public IdentifierCellRenderer(Role role) + { + mRole = role; + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof IdentifierCollection) + { + List identifiers = ((IdentifierCollection)value).getIdentifiers(mRole); + label.setText(format(identifiers)); + } + else + { + label.setText(null); + } + + return label; + } + + /** + * Formats a list of identifiers as a comma separated list of values + * @param identifiers to format + * @return formatted list or null + */ + protected String format(List identifiers) + { + if(identifiers == null || identifiers.isEmpty()) + { + return null; + } + + StringBuilder sb = new StringBuilder(); + + for(Identifier identifier: identifiers) + { + if(sb.length() > 0) + { + sb.append(","); + } + + if(identifier.getForm() == Form.TALKGROUP) + { + sb.append(mUserPreferences.getTalkgroupFormatPreference().format(identifier)); + } + else + { + sb.append(identifier); + } + + } + + return sb.toString(); + } + } + + /** + * Cell renderer for identifier aliases + */ + public class AliasedIdentifierCellRenderer extends DefaultTableCellRenderer + { + private Role mRole; + + /** + * Constructs an instance of the cell renderer. + * + * @param role of the identifier + */ + public AliasedIdentifierCellRenderer(Role role) + { + mRole = role; + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + Color color = mTable.getForeground(); + ImageIcon icon = null; + String text = null; + + if(value instanceof IdentifierCollection) + { + IdentifierCollection identifierCollection = (IdentifierCollection)value; + List identifiers = identifierCollection.getIdentifiers(mRole); + + if(identifiers != null && !identifiers.isEmpty()) + { + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); + + if(aliasList != null) + { + StringBuilder sb = new StringBuilder(); + + for(Identifier identifier: identifiers) + { + List aliases = aliasList.getAliases(identifier); + + if(!aliases.isEmpty()) + { + if(sb.length() > 0) + { + sb.append(","); + } + sb.append(Joiner.on(", ").skipNulls().join(aliases)); + color = aliases.get(0).getDisplayColor(); + icon = mIconModel.getIcon(aliases.get(0).getIconName(), IconModel.DEFAULT_ICON_SIZE); + } + } + + text = sb.toString(); + } + } + } + + label.setText(text); + label.setForeground(color); + label.setIcon(icon); + + return label; + } + } + + public class TimestampCellRenderer extends DefaultTableCellRenderer + { + private SimpleDateFormat mTimestampFormatter; + + public TimestampCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + updatePreferences(); + } + + public void updatePreferences() + { + mTimestampFormatter = mUserPreferences.getDecodeEventPreference().getTimestampFormat().getFormatter(); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof Long) + { + label.setText(mTimestampFormatter.format(new Date((long)value))); + } + else + { + label.setText(null); + } + + return label; + } + } + + public class DurationCellRenderer extends DefaultTableCellRenderer + { + private DecimalFormat mDecimalFormat = new DecimalFormat("0.0"); + + public DurationCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + String formatted = null; + + if(value instanceof Long) + { + long duration = (long)value; + + if(duration > 0) + { + formatted = mDecimalFormat.format((double)duration / 1e3d); + } + } + + label.setText(formatted); + + return label; + } + } + + /** + * Frequency value cell renderer + */ + public class FrequencyCellRenderer extends DefaultTableCellRenderer + { + private DecimalFormat mFrequencyFormatter = new DecimalFormat("0.00000"); + + public FrequencyCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + String formatted = null; + + if(value instanceof IChannelDescriptor) + { + IChannelDescriptor channelDescriptor = (IChannelDescriptor)value; + + long frequency = channelDescriptor.getDownlinkFrequency(); + + if(frequency > 0) + { + formatted = mFrequencyFormatter.format(frequency / 1e6d); + } + } + + label.setText(formatted); + + return label; + } + } + + /** + * Channel descriptor value cell renderer + */ + public class ChannelDescriptorCellRenderer extends DefaultTableCellRenderer + { + public ChannelDescriptorCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenFilterSet.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenFilterSet.java new file mode 100644 index 00000000..871fd567 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenFilterSet.java @@ -0,0 +1,17 @@ +package io.github.dsheirer.module.decode.event.filter.lastseen; + +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.module.decode.event.IDecodeEvent; + +public class LastSeenFilterSet extends FilterSet +{ + + public LastSeenFilterSet(AliasModel aliasModel, IconModel iconModel) + { + super("Last Seen From Radio"); + + addFilter(new LastSeenIconFilter("Icon", aliasModel, iconModel)); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistory.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistory.java new file mode 100644 index 00000000..a3d0d424 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistory.java @@ -0,0 +1,77 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.event.filter.lastseen; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.module.HistoryModule; +import io.github.dsheirer.module.decode.event.DecodeEventHistoryPreloadData; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.IDecodeEventListener; +import io.github.dsheirer.sample.Listener; + +/** + * Decode event history module. Maintains a history of decode events and constrains the total history size. + */ +public class LastSeenHistory extends HistoryModule implements IDecodeEventListener +{ + /** + * Constructs an instance + */ + public LastSeenHistory(int historySize) + { + super(historySize); + } + + /** + * Implements the IDecodeEventListener interface - delegates to receive(event) method. + */ + @Override + public Listener getDecodeEventListener() + { + return this; + } + + /** + * Process preload data + */ + @Subscribe + public void process(DecodeEventHistoryPreloadData preloadData) + { + for(IDecodeEvent decodeEvent: preloadData.getData()) + { + receive(decodeEvent); + } + } + + /** + * Processes a request for decode event history and posts the response back + * to the processing chain event bus so that any of the modules can receive that history. + * + * Note: this is principally used by the DMR decoder for Capacity+ REST channel rotation to transfer the decode + * event history from a traffic channel conversion to the new rest channel. + * + * @param request for decode event history + */ + @Subscribe + public void process(LastSeenHistoryRequest request) + { + getInterModuleEventBus().post(new LastSeenHistoryResponse(this)); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistoryRequest.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistoryRequest.java new file mode 100644 index 00000000..a9daa12e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistoryRequest.java @@ -0,0 +1,35 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.event.filter.lastseen; + +import io.github.dsheirer.module.ModuleEventBusMessage; + +/** + * Request for decode event history from a processing chain for the specified channel. + */ +public class LastSeenHistoryRequest extends ModuleEventBusMessage +{ + /** + * Constructs an instance + */ + public LastSeenHistoryRequest() + { + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistoryResponse.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistoryResponse.java new file mode 100644 index 00000000..8d85fa05 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenHistoryResponse.java @@ -0,0 +1,47 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2020 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * **************************************************************************** + */ + +package io.github.dsheirer.module.decode.event.filter.lastseen; + +import io.github.dsheirer.module.ModuleEventBusMessage; + +/** + * Response to decode event history request from a processing chain for the specified channel. + */ +public class LastSeenHistoryResponse extends ModuleEventBusMessage +{ + private LastSeenHistory mLastSeenHistory; + + /** + * Constructs an instance + * @param history that has a decode event history + */ + public LastSeenHistoryResponse(LastSeenHistory history) + { + mLastSeenHistory = history; + } + + /** + * Decode event history module + */ + public LastSeenHistory getDecodeEventHistory() + { + return mLastSeenHistory; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenIconFilter.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenIconFilter.java new file mode 100644 index 00000000..3089d4b0 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenIconFilter.java @@ -0,0 +1,96 @@ +package io.github.dsheirer.module.decode.event.filter.lastseen; + +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.filter.Filter; +import io.github.dsheirer.filter.FilterElement; +import io.github.dsheirer.icon.Icon; +import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import javafx.collections.ObservableList; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class LastSeenIconFilter extends Filter +{ + private AliasModel mAliasModel; + private Map> mElements = new HashMap<>(); + + public LastSeenIconFilter(String name, AliasModel aliasModel, IconModel iconModel) + { + super(name); + this.mAliasModel = aliasModel; + + ObservableList icons = iconModel.iconsProperty(); + for (Icon icon : icons) { + mElements.put(icon.getName(), new FilterElement<>(icon.getName())); + } + } + + @Override + public boolean passes(IDecodeEvent iDecodeEvent) + { + if (mEnabled && canProcess(iDecodeEvent)) + { + return matchAliasToFilter(iDecodeEvent) + .map(FilterElement::isEnabled) + .orElse(false); + } + return false; + } + + public Optional> matchAliasToFilter(IDecodeEvent iDecodeEvent) + { + IdentifierCollection identifierCollection = iDecodeEvent.getIdentifierCollection(); + List identifiers = identifierCollection.getIdentifiers(); + if (identifiers != null && !identifiers.isEmpty()) + { + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); + if (aliasList != null) + { + for(Identifier identifier: identifiers) + { + if (identifier.getForm() == Form.RADIO) + { + List aliases = aliasList.getAliases(identifier); + if (!aliases.isEmpty()) + { + for (Alias alias : aliases) { + if (alias.getIconName() == null) + { + // Return any to match aliases with no icon defined. + return Optional.of(mElements.get(IconModel.DEFAULT_ICON)); + } + else + { + return Optional.of(mElements.get(alias.getIconName())); + } + } + } + } + } + } + } + return Optional.empty(); + } + + @Override + public boolean canProcess(IDecodeEvent iDecodeEvent) + { + return true; + } + + @Override + public List> getFilterElements() + { + return new ArrayList<>(mElements.values()); + } +} \ No newline at end of file diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenModel.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenModel.java new file mode 100644 index 00000000..4b2506d7 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenModel.java @@ -0,0 +1,289 @@ +/* + * + * * ****************************************************************************** + * * Copyright (C) 2014-2019 Dennis Sheirer + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see + * * ***************************************************************************** + * + * + */ +package io.github.dsheirer.module.decode.event.filter.lastseen; + +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.filter.EventFilterProvider; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.sample.Listener; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class LastSeenModel extends AbstractTableModel implements Listener, EventFilterProvider +{ + private static final long serialVersionUID = 1L; + + public static final int COLUMN_TIME = 0; + public static final int COLUMN_DURATION = 1; + public static final int COLUMN_EVENT = 2; + public static final int COLUMN_FROM_ID = 3; + public static final int COLUMN_FROM_ALIAS = 4; + public static final int COLUMN_TO_ID = 5; + public static final int COLUMN_TO_ALIAS = 6; + public static final int COLUMN_CHANNEL = 7; + public static final int COLUMN_FREQUENCY = 8; + public static final int COLUMN_DETAILS = 9; + + protected int mMaxMessages = 500; + + protected List mEvents = new ArrayList<>(); + protected FilterSet mLastSeenFilterSet; + + protected String[] mHeaders = new String[]{"Time", "Duration", "Event", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Details"}; + + public LastSeenModel(AliasModel aliasModel, IconModel iconModel) + { + mLastSeenFilterSet = new LastSeenFilterSet(aliasModel, iconModel); + MyEventBus.getGlobalEventBus().register(this); + } + + /** + * Receives preference update notifications via the event bus + * @param preferenceType that was updated + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.DECODE_EVENT) + { + for(int row = 0; row < mEvents.size(); row++) + { + fireTableCellUpdated(row, COLUMN_TIME); + } + } + else if(preferenceType == PreferenceType.TALKGROUP_FORMAT) + { + for(int row = 0; row < mEvents.size(); row++) + { + fireTableCellUpdated(row, COLUMN_FROM_ID); + fireTableCellUpdated(row, COLUMN_TO_ID); + } + } + } + + /** + * Access the complete list of events managed by this model. + */ + public List getEvents() + { + return new ArrayList<>(mEvents); + } + + public void dispose() + { + MyEventBus.getGlobalEventBus().unregister(this); + Iterator it = mEvents.iterator(); + + while(it.hasNext()) + { + it.remove(); + } + } + + public void clear() + { + mEvents.clear(); + fireTableDataChanged(); + } + + /** + * Clears all events from this model and loads the events argument + */ + public void clearAndSet(List events) + { + mEvents.clear(); + mEvents.addAll(events); + fireTableDataChanged(); + } + + public void reset() + { + dispose(); + fireTableDataChanged(); + } + + public int getMaxMessageCount() + { + return mMaxMessages; + } + + public void setMaxMessageCount(int count) + { + mMaxMessages = count; + } + + /** + * Adds, updates or deletes the event from the model. Producers can send + * the same call event multiple times to indicate that information in the + * event is updated. Producers can also mark the event as invalid and the + * event will be removed from the model. + */ + public void receive(final IDecodeEvent event) + { + if (mLastSeenFilterSet != null && !mLastSeenFilterSet.passes(event)) + { + return; + } + if(!mEvents.contains(event)) + { + mEvents.add(0, event); + fireTableRowsInserted(0, 0); + prune(); + } + else + { + int row = mEvents.indexOf(event); + fireTableRowsUpdated(row, row); + } + } + + private void prune() + { + while(mEvents.size() > mMaxMessages) + { + int index = mEvents.size() - 1; + mEvents.remove(index); + fireTableRowsDeleted(index, index); + } + } + + @Override + public FilterSet getFilterSet() { + return mLastSeenFilterSet; + } + + @Override + public void setFilterSet(FilterSet filterSet) { + this.mLastSeenFilterSet = filterSet; + } + + @Override + public int getRowCount() + { + return mEvents.size(); + } + + @Override + public int getColumnCount() + { + return mHeaders.length; + } + + public String getColumnName(int column) + { + return mHeaders[column]; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) + { + synchronized(mEvents) + { + IDecodeEvent event = mEvents.get(rowIndex); + + if(event != null) + { + switch(columnIndex) + { + case COLUMN_TIME: + return event.getTimeStart(); + case COLUMN_DURATION: + return event.getDuration(); + case COLUMN_EVENT: + return event.getEventDescription(); + case COLUMN_FROM_ID: + return event.getIdentifierCollection(); + case COLUMN_FROM_ALIAS: + return event.getIdentifierCollection(); + case COLUMN_TO_ID: + return event.getIdentifierCollection(); + case COLUMN_TO_ALIAS: + return event.getIdentifierCollection(); + case COLUMN_CHANNEL: + IChannelDescriptor channelDescriptor = event.getChannelDescriptor(); + + if(channelDescriptor != null) + { + if(event.hasTimeslot()) + { + return channelDescriptor.toString() + " TS:" + event.getTimeslot(); + } + else + { + return channelDescriptor.toString(); + } + } + else + { + if(event.hasTimeslot()) + { + return "TS:" + event.getTimeslot(); + } + else + { + return null; + } + } + case COLUMN_FREQUENCY: + return event.getChannelDescriptor(); + case COLUMN_DETAILS: + return event.getDetails(); + } + } + } + + return null; + } + + @Override + public Class getColumnClass(int columnIndex) + { + switch(columnIndex) + { + case COLUMN_DURATION: + case COLUMN_TIME: + return Long.class; + case COLUMN_DETAILS: + case COLUMN_EVENT: + return String.class; + case COLUMN_FREQUENCY: + case COLUMN_FROM_ALIAS: + case COLUMN_FROM_ID: + case COLUMN_TO_ALIAS: + case COLUMN_TO_ID: + return IdentifierCollection.class; + case COLUMN_CHANNEL: + return String.class; + } + return super.getColumnClass(columnIndex); + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenPanel.java b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenPanel.java new file mode 100644 index 00000000..47057d13 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/event/filter/lastseen/LastSeenPanel.java @@ -0,0 +1,482 @@ +/* + * + * * ****************************************************************************** + * * Copyright (C) 2014-2019 Dennis Sheirer + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see + * * ***************************************************************************** + * + * + */ +package io.github.dsheirer.module.decode.event.filter.lastseen; + +import com.google.common.base.Joiner; +import com.google.common.eventbus.Subscribe; +import io.github.dsheirer.alias.Alias; +import io.github.dsheirer.alias.AliasList; +import io.github.dsheirer.alias.AliasModel; +import io.github.dsheirer.channel.IChannelDescriptor; +import io.github.dsheirer.eventbus.MyEventBus; +import io.github.dsheirer.filter.AllPassFilter; +import io.github.dsheirer.filter.FilterSet; +import io.github.dsheirer.icon.IconModel; +import io.github.dsheirer.identifier.Form; +import io.github.dsheirer.identifier.Identifier; +import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.identifier.Role; +import io.github.dsheirer.module.ProcessingChain; +import io.github.dsheirer.module.decode.event.DecodeEventHistory; +import io.github.dsheirer.module.decode.event.DecodeEventModel; +import io.github.dsheirer.module.decode.event.IDecodeEvent; +import io.github.dsheirer.module.decode.event.filter.EventClearButton; +import io.github.dsheirer.module.decode.event.filter.EventClearHandler; +import io.github.dsheirer.module.decode.event.filter.EventFilterButton; +import io.github.dsheirer.module.decode.event.filter.EventFilterProvider; +import io.github.dsheirer.preference.PreferenceType; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor; +import io.github.dsheirer.sample.Listener; +import net.miginfocom.swing.MigLayout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.*; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +public class LastSeenPanel extends JPanel implements Listener +{ + private static final long serialVersionUID = 1L; + private final static Logger mLog = LoggerFactory.getLogger(LastSeenPanel.class); + private static final String TABLE_PREFERENCE_KEY = "decode.lastseen.panel"; + + private JTable mTable; + private JTableColumnWidthMonitor mTableColumnWidthMonitor; + private LastSeenModel mLastSeenModel; + private DecodeEventHistory mCurrentEventHistory; + private JScrollPane mEmptyScroller; + private IconModel mIconModel; + private AliasModel mAliasModel; + private UserPreferences mUserPreferences; + private TimestampCellRenderer mTimestampCellRenderer; + private LastSeenManagementPanel mLastSeenManagementPanel; + + /** + * View for call event table + * @param iconModel to display alias icons in table rows + */ + public LastSeenPanel(IconModel iconModel, UserPreferences userPreferences, AliasModel aliasModel) + { + MyEventBus.getGlobalEventBus().register(this); + + setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]")); + mIconModel = iconModel; + mAliasModel = aliasModel; + mLastSeenModel = new LastSeenModel(aliasModel, iconModel); + mUserPreferences = userPreferences; + mTimestampCellRenderer = new TimestampCellRenderer(); + mTable = new JTable(mLastSeenModel); + mTable.setAutoCreateRowSorter(true); + mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + mLastSeenManagementPanel = new LastSeenManagementPanel(); + mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY); + updateCellRenderers(); + + add(mLastSeenManagementPanel, "span,growx"); + + mEmptyScroller = new JScrollPane(mTable); + add(mEmptyScroller); + } + + public void dispose() + { + MyEventBus.getGlobalEventBus().unregister(this); + } + + /** + * Receives preference update notifications via the event bus + * @param preferenceType that was updated + */ + @Subscribe + public void preferenceUpdated(PreferenceType preferenceType) + { + if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.TALKGROUP_FORMAT) + { + EventQueue.invokeLater(() -> mTimestampCellRenderer.updatePreferences()); + } + } + + private void updateCellRenderers() + { + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TIME) + .setCellRenderer(mTimestampCellRenderer); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_DURATION) + .setCellRenderer(new DurationCellRenderer()); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ID) + .setCellRenderer(new IdentifierCellRenderer(Role.FROM)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ALIAS) + .setCellRenderer(new AliasedIdentifierCellRenderer(Role.FROM)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ID) + .setCellRenderer(new IdentifierCellRenderer(Role.TO)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ALIAS) + .setCellRenderer(new AliasedIdentifierCellRenderer(Role.TO)); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_CHANNEL) + .setCellRenderer(new ChannelDescriptorCellRenderer()); + mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FREQUENCY) + .setCellRenderer(new FrequencyCellRenderer()); + } + + @Override + public void receive(final ProcessingChain processingChain) + { + if(mCurrentEventHistory != null) + { + mCurrentEventHistory.removeListener(mLastSeenModel); + } + + EventQueue.invokeLater(() -> { + if(processingChain != null) + { + mCurrentEventHistory = processingChain.getDecodeEventHistory(); + mLastSeenModel.clearAndSet(mCurrentEventHistory.getItems()); + processingChain.getDecodeEventHistory().addListener(mLastSeenModel); + mLastSeenManagementPanel.enableButtons(); + } + else + { + mCurrentEventHistory = null; + mLastSeenModel.clearAndSet(Collections.emptyList()); + mLastSeenManagementPanel.disableButtons(); + } + }); + } + + public class LastSeenManagementPanel extends JPanel + { + private static final long serialVersionUID = 1L; + + private EventFilterButton mFilterButton; + private EventClearButton mEventClearButton; + + public LastSeenManagementPanel() + { + setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", "")); + + initializeFilterButton(); + initializeClearButton(); + disableButtons(); + + add(mFilterButton); + add(mEventClearButton); + } + + public void enableButtons() + { + mFilterButton.setEnabled(true); + mEventClearButton.setEnabled(true); + } + + public void disableButtons() + { + mFilterButton.setEnabled(false); + mEventClearButton.setEnabled(false); + } + + private void initializeFilterButton() + { + EventFilterProvider filterProvider = (EventFilterProvider) mTable.getModel(); + FilterSet filterSet = new FilterSet<>(new AllPassFilter<>()); + if (filterProvider != null) { + filterSet = filterProvider.getFilterSet(); + } + mFilterButton = new EventFilterButton<>("Last Seen From Radio Filter Editor", filterSet); + } + + private void initializeClearButton() { + mEventClearButton = new EventClearButton( + ((LastSeenModel) mTable.getModel()).getMaxMessageCount() + ); + mEventClearButton.setEventClearHandler(new EventClearHandler() { + @Override + public void onHistoryLimitChanged(int newHistoryLimit) { + ((LastSeenModel) mTable.getModel()).setMaxMessageCount(newHistoryLimit); + } + + @Override + public void onClearHistoryClicked() { + ((LastSeenModel) mTable.getModel()).clear(); + } + }); + } + } + + /** + * Custom cell renderer for displaying identifiers from an identifier collection + */ + public class IdentifierCellRenderer extends DefaultTableCellRenderer + { + protected Role mRole; + + /** + * Constructs an instance of the cell renderer. + * + * @param role of the identifier + */ + public IdentifierCellRenderer(Role role) + { + mRole = role; + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof IdentifierCollection) + { + List identifiers = ((IdentifierCollection)value).getIdentifiers(mRole); + label.setText(format(identifiers)); + } + else + { + label.setText(null); + } + + return label; + } + + /** + * Formats a list of identifiers as a comma separated list of values + * @param identifiers to format + * @return formatted list or null + */ + protected String format(List identifiers) + { + if(identifiers == null || identifiers.isEmpty()) + { + return null; + } + + StringBuilder sb = new StringBuilder(); + + for(Identifier identifier: identifiers) + { + if(sb.length() > 0) + { + sb.append(","); + } + + if(identifier.getForm() == Form.TALKGROUP) + { + sb.append(mUserPreferences.getTalkgroupFormatPreference().format(identifier)); + } + else + { + sb.append(identifier); + } + + } + + return sb.toString(); + } + } + + /** + * Cell renderer for identifier aliases + */ + public class AliasedIdentifierCellRenderer extends DefaultTableCellRenderer + { + private Role mRole; + + /** + * Constructs an instance of the cell renderer. + * + * @param role of the identifier + */ + public AliasedIdentifierCellRenderer(Role role) + { + mRole = role; + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + Color color = mTable.getForeground(); + ImageIcon icon = null; + String text = null; + + if(value instanceof IdentifierCollection) + { + IdentifierCollection identifierCollection = (IdentifierCollection)value; + List identifiers = identifierCollection.getIdentifiers(mRole); + + if(identifiers != null && !identifiers.isEmpty()) + { + AliasList aliasList = mAliasModel.getAliasList(identifierCollection); + + if(aliasList != null) + { + StringBuilder sb = new StringBuilder(); + + for(Identifier identifier: identifiers) + { + List aliases = aliasList.getAliases(identifier); + + if(!aliases.isEmpty()) + { + if(sb.length() > 0) + { + sb.append(","); + } + sb.append(Joiner.on(", ").skipNulls().join(aliases)); + color = aliases.get(0).getDisplayColor(); + icon = mIconModel.getIcon(aliases.get(0).getIconName(), IconModel.DEFAULT_ICON_SIZE); + } + } + + text = sb.toString(); + } + } + } + + label.setText(text); + label.setForeground(color); + label.setIcon(icon); + + return label; + } + } + + public class TimestampCellRenderer extends DefaultTableCellRenderer + { + private SimpleDateFormat mTimestampFormatter; + + public TimestampCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + updatePreferences(); + } + + public void updatePreferences() + { + mTimestampFormatter = mUserPreferences.getDecodeEventPreference().getTimestampFormat().getFormatter(); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if(value instanceof Long) + { + label.setText(mTimestampFormatter.format(new Date((long)value))); + } + else + { + label.setText(null); + } + + return label; + } + } + + public class DurationCellRenderer extends DefaultTableCellRenderer + { + private DecimalFormat mDecimalFormat = new DecimalFormat("0.0"); + + public DurationCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + String formatted = null; + + if(value instanceof Long) + { + long duration = (long)value; + + if(duration > 0) + { + formatted = mDecimalFormat.format((double)duration / 1e3d); + } + } + + label.setText(formatted); + + return label; + } + } + + /** + * Frequency value cell renderer + */ + public class FrequencyCellRenderer extends DefaultTableCellRenderer + { + private DecimalFormat mFrequencyFormatter = new DecimalFormat("0.00000"); + + public FrequencyCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) + { + JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + String formatted = null; + + if(value instanceof IChannelDescriptor) + { + IChannelDescriptor channelDescriptor = (IChannelDescriptor)value; + + long frequency = channelDescriptor.getDownlinkFrequency(); + + if(frequency > 0) + { + formatted = mFrequencyFormatter.format(frequency / 1e6d); + } + } + + label.setText(formatted); + + return label; + } + } + + /** + * Channel descriptor value cell renderer + */ + public class ChannelDescriptorCellRenderer extends DefaultTableCellRenderer + { + public ChannelDescriptorCellRenderer() + { + setHorizontalAlignment(JLabel.CENTER); + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderState.java index 2a6ba768..ffd3fb8a 100644 --- a/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/fleetsync2/Fleetsync2DecoderState.java @@ -2,19 +2,24 @@ * ***************************************************************************** * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * * ****************************************************************************** + * * Copyright (C) 2014-2019 Dennis Sheirer + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with this program. If not, see + * * ***************************************************************************** * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** */ package io.github.dsheirer.module.decode.fleetsync2; @@ -107,13 +112,7 @@ public class Fleetsync2DecoderState extends DecoderState case ANI: case EMERGENCY: case LONE_WORKER_EMERGENCY: - DecodeEvent aniEvent = DecodeEvent.builder(fleetsync.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(fleetsync.getMessageType().toString()) - .details(fleetsync.toString()) - .identifiers(getIdentifierCollection().copyOf()) - .build(); - + DecodeEvent aniEvent = getDecodeEvent(fleetsync, getDecodeEventType(fleetsync.getMessageType())); broadcast(aniEvent); broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.CALL)); break; @@ -121,18 +120,14 @@ public class Fleetsync2DecoderState extends DecoderState case PAGING: case STATUS: case UNKNOWN: - DecodeEvent statusEvent = DecodeEvent.builder(fleetsync.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(fleetsync.getMessageType().toString()) - .details(fleetsync.toString()) - .identifiers(getIdentifierCollection().copyOf()) - .build(); + DecodeEvent statusEvent = getDecodeEvent(fleetsync, getDecodeEventType(fleetsync.getMessageType())); broadcast(statusEvent); broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.DATA)); break; case GPS: PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(fleetsync.getTimestamp()) .channel(getCurrentChannel()) + .eventType(DecodeEventType.GPS) .eventDescription(DecodeEventType.GPS.toString()) .details(fleetsync.toString()) .identifiers(getIdentifierCollection().copyOf()) @@ -147,6 +142,37 @@ public class Fleetsync2DecoderState extends DecoderState } } + private DecodeEvent getDecodeEvent(Fleetsync2Message fleetsync, DecodeEventType eventType) { + return DecodeEvent.builder(fleetsync.getTimestamp()) + .channel(getCurrentChannel()) + .eventType(eventType) + .eventDescription(fleetsync.getMessageType().toString()) + .details(fleetsync.toString()) + .identifiers(getIdentifierCollection().copyOf()) + .protocol(Protocol.FLEETSYNC) + .build(); + } + + private DecodeEventType getDecodeEventType(FleetsyncMessageType fleetsyncMessageType) { + switch (fleetsyncMessageType) { + case ANI: + return DecodeEventType.ID_ANI; + case EMERGENCY: + case LONE_WORKER_EMERGENCY: + return DecodeEventType.EMERGENCY; + case ACKNOWLEDGE: + return DecodeEventType.ACKNOWLEDGE; + case PAGING: + return DecodeEventType.PAGE; + case STATUS: + return DecodeEventType.STATUS; + case GPS: + return DecodeEventType.GPS; + default: + return DecodeEventType.UNKNOWN; + } + } + /** * Responds to reset events issued by the channel state */ diff --git a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderState.java index 9763f463..258970fa 100644 --- a/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/lj1200/LJ1200DecoderState.java @@ -30,6 +30,7 @@ import io.github.dsheirer.identifier.MutableIdentifierCollection; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +75,7 @@ public class LJ1200DecoderState extends DecoderState DecodeEvent event = DecodeEvent.builder(System.currentTimeMillis()) .identifiers(ic) + .eventType(DecodeEventType.DATA_PACKET) .channel(getCurrentChannel()) .details("LOJACK") .eventDescription(lj.toString()) @@ -93,6 +95,7 @@ public class LJ1200DecoderState extends DecoderState DecodeEvent transponderEvent = DecodeEvent.builder(System.currentTimeMillis()) .identifiers(ic) + .eventType(DecodeEventType.GPS) .channel(getCurrentChannel()) .details("LOJACK TRANSPONDER") .eventDescription(transponder.toString()) diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecodeEvent.java new file mode 100644 index 00000000..5a7c6b79 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecodeEvent.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ + +package io.github.dsheirer.module.decode.ltrnet; + +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.protocol.Protocol; + +public class LTRNetDecodeEvent extends DecodeEvent +{ + /** + * Constructs an LTR NET decode event + * @param start + */ + public LTRNetDecodeEvent(long start) + { + super(start); + setProtocol(Protocol.LTR_NET); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart); + decodeEventBuilder.protocol(Protocol.LTR_NET); + return decodeEventBuilder; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderState.java index c29402ae..45c26187 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrnet/LTRNetDecoderState.java @@ -38,6 +38,7 @@ import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageDirection; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.ltrnet.channel.LtrNetChannel; import io.github.dsheirer.module.decode.ltrnet.identifier.LtrNetRadioIdentifier; import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage; @@ -168,8 +169,7 @@ public class LTRNetDecoderState extends DecoderState { getIdentifierCollection().remove(IdentifierClass.USER); getIdentifierCollection().update(talkgroup); - mCurrentCallEvent = DecodeEvent.builder(timestamp) - .protocol(Protocol.LTR_NET) + mCurrentCallEvent = LTRNetDecodeEvent.builder(timestamp) .channel(getCurrentChannel()) .identifiers(getIdentifierCollection().copyOf()) .build(); @@ -181,16 +181,19 @@ public class LTRNetDecoderState extends DecoderState if(talkgroup.getTalkgroup() == 253) { + mCurrentCallEvent.setEventType(DecodeEventType.REGISTER); mCurrentCallEvent.setEventDescription("Register"); broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); } else if(talkgroup.getTalkgroup() == 254) { + mCurrentCallEvent.setEventType(DecodeEventType.STATION_ID); mCurrentCallEvent.setEventDescription("FCC CWID"); broadcast(new DecoderStateEvent(this, Event.START, State.DATA)); } else { + mCurrentCallEvent.setEventType(DecodeEventType.CALL); mCurrentCallEvent.setEventDescription("Call"); broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } @@ -203,16 +206,7 @@ public class LTRNetDecoderState extends DecoderState if(decodeEvent == null) { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - ic.remove(IdentifierClass.USER); - ic.update(talkgroup); - - decodeEvent = DecodeEvent.builder(timestamp) - .eventDescription("Call Detect") - .channel(mChannelMap.get(channel)) - .protocol(Protocol.LTR_NET) - .identifiers(ic) - .build(); + decodeEvent = getDecodeEvent(talkgroup, channel, timestamp, DecodeEventType.CALL_DETECT); mCallDetectMap.put(channel, decodeEvent); } else @@ -223,16 +217,7 @@ public class LTRNetDecoderState extends DecoderState if(eventTalkgroup == null || !eventTalkgroup.equals(talkgroup) || (timestamp - decodeEvent.getTimeStart() - decodeEvent.getDuration() > 2000)) { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - ic.remove(IdentifierClass.USER); - ic.update(talkgroup); - - decodeEvent = DecodeEvent.builder(timestamp) - .eventDescription("Call Detect") - .channel(mChannelMap.get(channel)) - .protocol(Protocol.LTR_NET) - .identifiers(ic) - .build(); + decodeEvent = getDecodeEvent(talkgroup, channel, timestamp, DecodeEventType.CALL_DETECT); mCallDetectMap.put(channel, decodeEvent); } } @@ -334,13 +319,9 @@ public class LTRNetDecoderState extends DecoderState mESNIdentifiers.add(registrationRequestEsn.getESN()); } - broadcast(DecodeEvent.builder(message.getTimestamp()) - .eventDescription("Registration Request") - .details(registrationRequestEsn.toString()) - .identifiers(getIdentifierCollection().copyOf()) - .channel(getCurrentChannel()) - .protocol(Protocol.LTR_NET) - .build()); + broadcast(getDecodeEvent( + message, DecodeEventType.REQUEST, + "Registration Request", registrationRequestEsn.toString())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); } @@ -356,13 +337,9 @@ public class LTRNetDecoderState extends DecoderState mESNIdentifiers.add(registrationRequestEsn.getESN()); } - broadcast(DecodeEvent.builder(message.getTimestamp()) - .eventDescription("Registration Request") - .details(registrationRequestEsn.toString()) - .identifiers(getIdentifierCollection().copyOf()) - .channel(getCurrentChannel()) - .protocol(Protocol.LTR_NET) - .build()); + broadcast(getDecodeEvent( + message, DecodeEventType.REQUEST, + "Registration Request", registrationRequestEsn.toString())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); } @@ -375,13 +352,9 @@ public class LTRNetDecoderState extends DecoderState getIdentifierCollection().update(requestAccess.getTalkgroup()); mTalkgroups.add(requestAccess.getTalkgroup()); - broadcast(DecodeEvent.builder(message.getTimestamp()) - .eventDescription("Access Request") - .details(requestAccess.toString()) - .identifiers(getIdentifierCollection().copyOf()) - .channel(getCurrentChannel()) - .protocol(Protocol.LTR_NET) - .build()); + broadcast(getDecodeEvent( + message, DecodeEventType.REQUEST, + "Access Request", requestAccess.toString())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA)); } @@ -470,13 +443,8 @@ public class LTRNetDecoderState extends DecoderState ic.remove(IdentifierClass.USER); ic.update(message.getIdentifiers()); - broadcast(DecodeEvent.builder(message.getTimestamp()) - .protocol(Protocol.LTR_NET) - .channel(getCurrentChannel()) - .identifiers(ic) - .eventDescription("Registration Accept") - .details(registrationAccept.toString()) - .build()); + broadcast(getDecodeEvent(message, DecodeEventType.RESPONSE, + "Registration Accept", registrationAccept.toString())); } break; case OSW_SITE_ID: @@ -509,6 +477,29 @@ public class LTRNetDecoderState extends DecoderState } } + private DecodeEvent getDecodeEvent(IMessage message, DecodeEventType decodeEventType, String description, String details) { + return LTRNetDecodeEvent.builder(message.getTimestamp()) + .eventType(decodeEventType) + .eventDescription(description) + .details(details) + .identifiers(getIdentifierCollection().copyOf()) + .channel(getCurrentChannel()) + .build(); + } + + private DecodeEvent getDecodeEvent(LTRTalkgroup talkgroup, int channel, long timestamp, DecodeEventType decodeEventType) { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(talkgroup); + + return LTRNetDecodeEvent.builder(timestamp) + .eventType(decodeEventType) + .eventDescription(decodeEventType.toString()) + .channel(mChannelMap.get(channel)) + .identifiers(ic) + .build(); + } + @Override public String getActivitySummary() { diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecodeEvent.java new file mode 100644 index 00000000..67a07504 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecodeEvent.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ + +package io.github.dsheirer.module.decode.ltrstandard; + +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.protocol.Protocol; + +public class LTRStandardDecodeEvent extends DecodeEvent +{ + /** + * Constructs an LTR Standard decode event + * @param start + */ + public LTRStandardDecodeEvent(long start) + { + super(start); + setProtocol(Protocol.LTR); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart); + decodeEventBuilder.protocol(Protocol.LTR); + return decodeEventBuilder; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoderState.java index f5539d21..7d8e2b08 100644 --- a/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/ltrstandard/LTRStandardDecoderState.java @@ -35,6 +35,7 @@ import io.github.dsheirer.identifier.talkgroup.LTRTalkgroup; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.ltrstandard.channel.LtrChannel; import io.github.dsheirer.module.decode.ltrstandard.message.Call; import io.github.dsheirer.module.decode.ltrstandard.message.CallEnd; @@ -97,10 +98,10 @@ public class LTRStandardDecoderState extends DecoderState mCurrentTalkgroup = start.getTalkgroup(); getIdentifierCollection().remove(IdentifierClass.USER); getIdentifierCollection().update(start.getTalkgroup()); - mCurrentCallEvent = DecodeEvent.builder(start.getTimestamp()) - .protocol(Protocol.LTR) + mCurrentCallEvent = LTRStandardDecodeEvent.builder(start.getTimestamp()) .identifiers(getIdentifierCollection().copyOf()) .channel(getCurrentChannel()) + .eventType(DecodeEventType.CALL) .eventDescription("Call") .build(); } diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecodeEvent.java new file mode 100644 index 00000000..bd7514f8 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecodeEvent.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ + +package io.github.dsheirer.module.decode.mdc1200; + +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.protocol.Protocol; + +public class MDCDecodeEvent extends DecodeEvent +{ + /** + * Constucts a P25 decode event + * @param start + */ + public MDCDecodeEvent(long start) + { + super(start); + setProtocol(Protocol.MDC1200); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart); + decodeEventBuilder.protocol(Protocol.MDC1200); + return decodeEventBuilder; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderState.java index f79fefd8..f4254e2f 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/mdc1200/MDCDecoderState.java @@ -29,7 +29,7 @@ import io.github.dsheirer.identifier.IdentifierClass; import io.github.dsheirer.identifier.MutableIdentifierCollection; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; -import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.mdc1200.identifier.MDC1200Identifier; import java.util.Iterator; @@ -114,7 +114,8 @@ public class MDCDecoderState extends DecoderState ic.remove(IdentifierClass.USER); ic.update(message.getIdentifiers()); - broadcast(DecodeEvent.builder(mdc.getTimestamp()) + broadcast(MDCDecodeEvent.builder(mdc.getTimestamp()) + .eventType(getDecodeEventType(type)) .eventDescription(type.getLabel()) .details(mdc.toString()) .identifiers(ic) @@ -195,4 +196,22 @@ public class MDCDecoderState extends DecoderState break; } } + + private DecodeEventType getDecodeEventType(MDCMessageType messageType) { + switch(messageType) + { + case ANI: + return DecodeEventType.ID_ANI; + case ACKNOWLEDGE: + return DecodeEventType.RESPONSE; + case EMERGENCY: + return DecodeEventType.EMERGENCY; + case PAGING: + return DecodeEventType.PAGE; + case STATUS: + return DecodeEventType.STATUS; + default: + return DecodeEventType.UNKNOWN; + } + } } diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java index 18506fcc..e65fe219 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327ChannelGrantEvent.java @@ -21,6 +21,7 @@ package io.github.dsheirer.module.decode.mpt1327; import io.github.dsheirer.channel.IChannelDescriptor; import io.github.dsheirer.identifier.IdentifierCollection; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.p25.P25DecodeEvent; import io.github.dsheirer.protocol.Protocol; @@ -49,6 +50,7 @@ public class MPT1327ChannelGrantEvent extends P25DecodeEvent protected long mTimeStart; protected long mDuration; protected String mEventDescription; + protected DecodeEventType mDecodeEventType; protected IdentifierCollection mIdentifierCollection; protected IChannelDescriptor mChannelDescriptor; protected String mDetails; @@ -93,6 +95,15 @@ public class MPT1327ChannelGrantEvent extends P25DecodeEvent return this; } + /** + * Sets the Decode Event type for this event. + * @param eventType + */ + public MPT1327ChannelGrantDecodeEventBuilder eventType(DecodeEventType eventType) { + mDecodeEventType = eventType; + return this; + } + /** * Sets the event description text * @param description of the event @@ -134,6 +145,7 @@ public class MPT1327ChannelGrantEvent extends P25DecodeEvent decodeEvent.setChannelDescriptor(mChannelDescriptor); decodeEvent.setDetails(mDetails); decodeEvent.setDuration(mDuration); + decodeEvent.setEventType(mDecodeEventType); decodeEvent.setEventDescription(mEventDescription); decodeEvent.setIdentifierCollection(mIdentifierCollection); return decodeEvent; diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecodeEvent.java new file mode 100644 index 00000000..3a662e4e --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecodeEvent.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ + +package io.github.dsheirer.module.decode.mpt1327; + +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.protocol.Protocol; + +public class MPT1327DecodeEvent extends DecodeEvent +{ + /** + * Constructs a MPT1327 decode event + * @param start + */ + public MPT1327DecodeEvent(long start) + { + super(start); + setProtocol(Protocol.MPT1327); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart); + decodeEventBuilder.protocol(Protocol.MPT1327); + return decodeEventBuilder; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderState.java index 6cdf8018..59fd1d9e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327DecoderState.java @@ -30,8 +30,8 @@ import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.mpt1327.channel.MPT1327Channel; -import io.github.dsheirer.protocol.Protocol; import io.github.dsheirer.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,28 +110,11 @@ public class MPT1327DecoderState extends DecoderState if(identType == MPT1327Message.IdentType.REGI) { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - ic.remove(IdentifierClass.USER); - ic.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(message.getTimestamp()) - .protocol(Protocol.MPT1327) - .eventDescription("Register") - .identifiers(ic) - .build()); + broadcast(getDecodeEvent(message, mpt, DecodeEventType.REGISTER, null)); } else { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - ic.remove(IdentifierClass.USER); - ic.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(message.getTimestamp()) - .protocol(Protocol.MPT1327) - .eventDescription("Response") - .details("ACK " + identType.getLabel()) - .identifiers(ic) - .build()); + broadcast(getDecodeEvent(message, mpt, DecodeEventType.RESPONSE, "ACK " + identType.getLabel())); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); @@ -143,45 +126,15 @@ public class MPT1327DecoderState extends DecoderState case ACKT: case ACKV: case ACKX: - MutableIdentifierCollection icACK = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icACK.remove(IdentifierClass.USER); - icACK.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(message.getTimestamp()) - .protocol(Protocol.MPT1327) - .eventDescription("Acknowledge") - .details(mpt.getMessageType().getDescription()) - .identifiers(icACK) - .build()); - + broadcast(getDecodeEvent(message, mpt, DecodeEventType.ACKNOWLEDGE, mpt.getMessageType().getDescription())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case AHYC: - MutableIdentifierCollection icAHY = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icAHY.remove(IdentifierClass.USER); - icAHY.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(message.getTimestamp()) - .protocol(Protocol.MPT1327) - .eventDescription("Command") - .details("Send Short Data Message") - .identifiers(icAHY) - .build()); - + broadcast(getDecodeEvent(message, mpt, DecodeEventType.COMMAND, "Send Short Data Message")); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case AHYQ: - MutableIdentifierCollection icAHYQ = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icAHYQ.remove(IdentifierClass.USER); - icAHYQ.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(message.getTimestamp()) - .protocol(Protocol.MPT1327) - .eventDescription("Command") - .details("Send Status Message") - .identifiers(icAHYQ) - .build()); - + broadcast(getDecodeEvent(message, mpt, DecodeEventType.COMMAND, "Send Status Message")); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case ALH: @@ -197,41 +150,22 @@ public class MPT1327DecoderState extends DecoderState case GTC: if(mMPT1327TrafficChannelManager != null) { - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - ic.remove(IdentifierClass.USER); - ic.update(mpt.getIdentifiers()); + MutableIdentifierCollection ic = getUpdatedMutableIdentifierCollection(mpt); mMPT1327TrafficChannelManager.processChannelGrant(mpt, ic); } else { MPT1327Channel channel = MPT1327Channel.create(mpt.getChannel()); - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - ic.remove(IdentifierClass.USER); - ic.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(mpt.getTimestamp()) - .eventDescription("Call Detect") - .details(mpt.getMessage()) - .channel(channel) - .identifiers(ic) - .build()); + DecodeEvent decodeEvent = getDecodeEvent(message, mpt, DecodeEventType.CALL_DETECT, mpt.getMessage()); + decodeEvent.setChannelDescriptor(channel); + broadcast(decodeEvent); } break; case HEAD_PLUS1: case HEAD_PLUS2: case HEAD_PLUS3: case HEAD_PLUS4: - MutableIdentifierCollection icHEAD = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icHEAD.remove(IdentifierClass.USER); - icHEAD.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(message.getTimestamp()) - .protocol(Protocol.MPT1327) - .eventDescription("Short Data Message") - .details(mpt.getMessage()) - .identifiers(icHEAD) - .build()); - + broadcast(getDecodeEvent(message, mpt, DecodeEventType.SDM, mpt.getMessage())); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; @@ -250,14 +184,7 @@ public class MPT1327DecoderState extends DecoderState // timeout specified by the user. Otherwise we'll be using the shorter default call timeout broadcast(new ChangeChannelTimeoutEvent(this, mChannelType, mCallTimeoutMilliseconds)); - MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - ic.remove(IdentifierClass.USER); - ic.update(mpt.getIdentifiers()); - - broadcast(DecodeEvent.builder(mpt.getTimestamp()) - .identifiers(ic) - .eventDescription("Call In Progress") - .build()); + broadcast(getDecodeEvent(message, mpt, DecodeEventType.CALL_IN_PROGRESS, null)); broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } break; @@ -268,6 +195,24 @@ public class MPT1327DecoderState extends DecoderState } } + private DecodeEvent getDecodeEvent(IMessage message, MPT1327Message mpt, DecodeEventType decodeEventType, String details) + { + MutableIdentifierCollection ic = getUpdatedMutableIdentifierCollection(mpt); + return MPT1327DecodeEvent.builder(message.getTimestamp()) + .eventType(decodeEventType) + .eventDescription(decodeEventType.toString()) + .details(details) + .identifiers(ic) + .build(); + } + + private MutableIdentifierCollection getUpdatedMutableIdentifierCollection(MPT1327Message mpt) { + MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + ic.remove(IdentifierClass.USER); + ic.update(mpt.getIdentifiers()); + return ic; + } + public void reset() { super.reset(); diff --git a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java index 713600f0..b59ef8b4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java +++ b/src/main/java/io/github/dsheirer/module/decode/mpt1327/MPT1327TrafficChannelManager.java @@ -30,6 +30,7 @@ import io.github.dsheirer.identifier.IdentifierCollection; import io.github.dsheirer.identifier.Role; import io.github.dsheirer.module.decode.config.DecodeConfiguration; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.IDecodeEvent; import io.github.dsheirer.module.decode.event.IDecodeEventProvider; import io.github.dsheirer.module.decode.mpt1327.channel.MPT1327Channel; @@ -105,6 +106,7 @@ public class MPT1327TrafficChannelManager extends TrafficChannelManager implemen MPT1327ChannelGrantEvent channelGrantEvent = MPT1327ChannelGrantEvent.mpt1327Builder(mpt1327Message.getTimestamp()) .channel(mpt1327Channel) + .eventType(DecodeEventType.CALL) .eventDescription("Call") .details("Traffic Channel Grant") .identifiers(identifierCollection) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java index 8189e25d..6ce0090e 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/P25DecodeEvent.java @@ -26,7 +26,7 @@ import io.github.dsheirer.protocol.Protocol; public class P25DecodeEvent extends DecodeEvent { /** - * Constucts a P25 decode event + * Constructs a P25 decode event * @param start */ public P25DecodeEvent(long start) diff --git a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java index 19daf59e..f735294c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/p25/phase2/P25P2DecoderState.java @@ -471,36 +471,19 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof QueuedResponse) { QueuedResponse qr = (QueuedResponse)mac; - - MutableIdentifierCollection icQueuedResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icQueuedResponse.remove(IdentifierClass.USER); - icQueuedResponse.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .details("QUEUED - " + qr.getQueuedResponseServiceType() + - " REASON:" + qr.getQueuedResponseReason() + " ADDL:" + qr.getAdditionalInfo()) - .identifiers(icQueuedResponse) - .build()); + broadcast(message, mac, DecodeEventType.RESPONSE, + "QUEUED - " + qr.getQueuedResponseServiceType() + + " REASON:" + qr.getQueuedResponseReason() + " ADDL:" + qr.getAdditionalInfo()); } } + private void processEFCA(MacMessage message, MacStructure mac) { if(mac instanceof ExtendedFunctionCommand) { ExtendedFunctionCommand efc = (ExtendedFunctionCommand)mac; - - MutableIdentifierCollection icExtendedFunction = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icExtendedFunction.remove(IdentifierClass.USER); - icExtendedFunction.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("EXTENDED FUNCTION: " + efc.getExtendedFunction() + " ARGUMENTS:" + efc.getArguments()) - .identifiers(icExtendedFunction) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "EXTENDED FUNCTION: " + efc.getExtendedFunction() + " ARGUMENTS:" + efc.getArguments()); } } @@ -508,16 +491,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof DenyResponse) { DenyResponse dr = (DenyResponse)mac; - MutableIdentifierCollection icDenyResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icDenyResponse.remove(IdentifierClass.USER); - icDenyResponse.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.RESPONSE.toString()) - .details("DENY: " + dr.getDeniedServiceType() + " REASON:" + dr.getDenyReason() + " ADDL:" + dr.getAdditionalInfo()) - .identifiers(icDenyResponse) - .build()); + broadcast(message, mac, DecodeEventType.RESPONSE, + "DENY: " + dr.getDeniedServiceType() + " REASON:" + dr.getDenyReason() + " ADDL:" + dr.getAdditionalInfo()); } } @@ -525,17 +500,9 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof RadioUnitMonitorCommandExtended) { RadioUnitMonitorCommandExtended rumce = (RadioUnitMonitorCommandExtended)mac; - MutableIdentifierCollection icRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icRequest.remove(IdentifierClass.USER); - icRequest.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("RADIO UNIT MONITOR" + (rumce.isSilentMonitor() ? " (STEALTH)" : "") + - " TIME:" + rumce.getTransmitTime() + "MULTIPLIER:" + rumce.getTransmitMultiplier()) - .identifiers(icRequest) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "RADIO UNIT MONITOR" + (rumce.isSilentMonitor() ? " (STEALTH)" : "") + + " TIME:" + rumce.getTransmitTime() + "MULTIPLIER:" + rumce.getTransmitMultiplier()); } } @@ -543,16 +510,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof StatusUpdateExtended) { StatusUpdateExtended sue = (StatusUpdateExtended)mac; - MutableIdentifierCollection icStatusUpdate = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icStatusUpdate.remove(IdentifierClass.USER); - icStatusUpdate.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.STATUS.toString()) - .details("STATUS UPDATE - UNIT:" + sue.getUnitStatus() + " USER:" + sue.getUserStatus()) - .identifiers(icStatusUpdate) - .build()); + broadcast(message, mac, DecodeEventType.STATUS, + "STATUS UPDATE - UNIT:" + sue.getUnitStatus() + " USER:" + sue.getUserStatus()); } } @@ -560,48 +519,23 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof ExtendedFunctionCommandExtended) { ExtendedFunctionCommandExtended efce = (ExtendedFunctionCommandExtended)mac; - - MutableIdentifierCollection icExtendedFunction = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icExtendedFunction.remove(IdentifierClass.USER); - icExtendedFunction.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("EXTENDED FUNCTION: " + efce.getExtendedFunction() + " ARGUMENTS:" + efce.getArguments()) - .identifiers(icExtendedFunction) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "EXTENDED FUNCTION: " + efce.getExtendedFunction() + " ARGUMENTS:" + efce.getArguments()); } } private void processCallAlert(MacMessage message, MacStructure mac) { - MutableIdentifierCollection icCallAlert = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icCallAlert.remove(IdentifierClass.USER); - icCallAlert.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.CALL_ALERT.toString()) - .identifiers(icCallAlert) - .build()); + broadcast(message, mac, DecodeEventType.CALL_ALERT, null); } private void processRUMCE(MacMessage message, MacStructure mac) { if(mac instanceof RadioUnitMonitorCommandEnhanced) { RadioUnitMonitorCommandEnhanced rumc = (RadioUnitMonitorCommandEnhanced)mac; - MutableIdentifierCollection icRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icRequest.remove(IdentifierClass.USER); - icRequest.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("RADIO UNIT MONITOR" + (rumc.isStealthMode() ? " (STEALTH)" : "") + + broadcast(message, mac, DecodeEventType.COMMAND, + "RADIO UNIT MONITOR" + (rumc.isStealthMode() ? " (STEALTH)" : "") + " ENCRYPTION:" + rumc.getEncryption() + - " TIME:" + rumc.getTransmitTime()) - .identifiers(icRequest) - .build()); + " TIME:" + rumc.getTransmitTime()); } } @@ -609,16 +543,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof StatusUpdateAbbreviated) { StatusUpdateAbbreviated sua = (StatusUpdateAbbreviated)mac; - MutableIdentifierCollection icStatusUpdate = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icStatusUpdate.remove(IdentifierClass.USER); - icStatusUpdate.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.STATUS.toString()) - .details("STATUS UPDATE - UNIT:" + sua.getUnitStatus() + " USER:" + sua.getUserStatus()) - .identifiers(icStatusUpdate) - .build()); + broadcast(message, mac, DecodeEventType.STATUS, + "STATUS UPDATE - UNIT:" + sua.getUnitStatus() + " USER:" + sua.getUserStatus()); } } @@ -626,16 +552,9 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof SNDCPDataChannelGrant) { SNDCPDataChannelGrant sdcg = (SNDCPDataChannelGrant)mac; - MutableIdentifierCollection icGrant = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icGrant.remove(IdentifierClass.USER); - icGrant.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(sdcg.getChannel()) - .eventDescription(sdcg.getServiceOptions().isEncrypted() ? DecodeEventType.DATA_CALL_ENCRYPTED.toString() : DecodeEventType.DATA_CALL.toString()) - .details("SNDCP CHANNEL GRANT " + sdcg.getServiceOptions()) - .identifiers(icGrant) - .build()); + broadcast(message, mac, + sdcg.getServiceOptions().isEncrypted() ? DecodeEventType.DATA_CALL_ENCRYPTED : DecodeEventType.DATA_CALL, + "SNDCP CHANNEL GRANT " + sdcg.getServiceOptions()); } } @@ -643,17 +562,9 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof RadioUnitMonitorCommand) { RadioUnitMonitorCommand rumc = (RadioUnitMonitorCommand)mac; - MutableIdentifierCollection icRequest = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icRequest.remove(IdentifierClass.USER); - icRequest.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("RADIO UNIT MONITOR" + (rumc.isSilentMonitor() ? " (STEALTH)" : "") + - " TIME:" + rumc.getTransmitTime() + " MULTIPLIER:" + rumc.getTransmitMultiplier()) - .identifiers(icRequest) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "RADIO UNIT MONITOR" + (rumc.isSilentMonitor() ? " (STEALTH)" : "") + + " TIME:" + rumc.getTransmitTime() + " MULTIPLIER:" + rumc.getTransmitMultiplier()); } } @@ -661,19 +572,9 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof MacRelease) { MacRelease mr = (MacRelease)mac; - - MutableIdentifierCollection icMacRelease = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icMacRelease.remove(IdentifierClass.USER); - icMacRelease.update(mac.getIdentifiers()); - closeCurrentCallEvent(message.getTimestamp(), true, message.getMacPduType()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details((mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" + (mr.isTalkerPreemption() ? " BY USER" : "")) - .identifiers(icMacRelease) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + (mr.isForcedPreemption() ? "FORCED " : "") + "CALL PREEMPTION" + (mr.isTalkerPreemption() ? " BY USER" : "")); } } @@ -681,17 +582,8 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof PowerControlSignalQuality) { PowerControlSignalQuality pcsq = (PowerControlSignalQuality)mac; - - MutableIdentifierCollection icPowerControl = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icPowerControl.remove(IdentifierClass.USER); - icPowerControl.update(mac.getIdentifiers()); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.COMMAND.toString()) - .details("ADJUST TRANSMIT POWER - RF:" + pcsq.getRFLevel() + " BER:" + pcsq.getBitErrorRate()) - .identifiers(icPowerControl) - .build()); + broadcast(message, mac, DecodeEventType.COMMAND, + "ADJUST TRANSMIT POWER - RF:" + pcsq.getRFLevel() + " BER:" + pcsq.getBitErrorRate()); } } @@ -699,34 +591,44 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie if(mac instanceof IndividualPagingMessage) { IndividualPagingMessage ipm = (IndividualPagingMessage)mac; - MutableIdentifierCollection icIndividualPaging = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - icIndividualPaging.remove(IdentifierClass.USER); - icIndividualPaging.update(mac.getIdentifiers()); boolean priority = ipm.isTalkgroupPriority1() || ipm.isTalkgroupPriority2() || ipm.isTalkgroupPriority3() || ipm.isTalkgroupPriority4(); - - broadcast(P25DecodeEvent.builder(message.getTimestamp()) - .channel(getCurrentChannel()) - .eventDescription(DecodeEventType.PAGE.toString()) - .details((priority ? "PRIORITY " : "") + "USER PAGE") - .identifiers(icIndividualPaging) - .build()); + broadcast(message, mac, DecodeEventType.PAGE, + (priority ? "PRIORITY " : "") + "USER PAGE"); } } - private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor currentChannel, DecodeEventType page, String s) { - MutableIdentifierCollection collection = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); - collection.remove(IdentifierClass.USER); - collection.update(mac.getIdentifiers()); + private void broadcast(MacMessage message, MacStructure mac, IChannelDescriptor currentChannel, DecodeEventType eventType, String details) { + MutableIdentifierCollection collection = getUpdatedMutableIdentifierCollection(mac); broadcast(P25DecodeEvent.builder(message.getTimestamp()) .channel(currentChannel) - .eventDescription(page.toString()) - .details(s) + .eventType(eventType) + .eventDescription(eventType.toString()) + .details(details) .identifiers(collection) .build()); } + private void broadcast(MacMessage message, MacStructure structure, DecodeEventType eventType, String details) { + MutableIdentifierCollection icQueuedResponse = getUpdatedMutableIdentifierCollection(structure); + + broadcast(P25DecodeEvent.builder(message.getTimestamp()) + .channel(getCurrentChannel()) + .eventType(eventType) + .eventDescription(eventType.toString()) + .details(details) + .identifiers(icQueuedResponse) + .build()); + } + + private MutableIdentifierCollection getUpdatedMutableIdentifierCollection(MacStructure mac) { + MutableIdentifierCollection icQueuedResponse = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers()); + icQueuedResponse.remove(IdentifierClass.USER); + icQueuedResponse.update(mac.getIdentifiers()); + return icQueuedResponse; + } + private void processUTUVCGUExtended(MacStructure mac) { if(getCurrentChannel() == null && mac instanceof UnitToUnitVoiceChannelGrantUpdateExtended) { diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecodeEvent.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecodeEvent.java new file mode 100644 index 00000000..49063945 --- /dev/null +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecodeEvent.java @@ -0,0 +1,49 @@ +/* + * ****************************************************************************** + * sdrtrunk + * Copyright (C) 2014-2018 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * ***************************************************************************** + */ + +package io.github.dsheirer.module.decode.passport; + +import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.protocol.Protocol; + +public class PassportDecodeEvent extends DecodeEvent +{ + /** + * Constructs a Passport decode event + * @param start + */ + public PassportDecodeEvent(long start) + { + super(start); + setProtocol(Protocol.PASSPORT); + } + + /** + * Creates a new decode event builder with the specified start timestamp. + * @param timeStart for the event + * @return builder + */ + public static DecodeEventBuilder builder(long timeStart) + { + DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart); + decodeEventBuilder.protocol(Protocol.PASSPORT); + return decodeEventBuilder; + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoderState.java b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoderState.java index 9663b909..a7bb9dd8 100644 --- a/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/passport/PassportDecoderState.java @@ -34,6 +34,7 @@ import io.github.dsheirer.message.IMessage; import io.github.dsheirer.message.MessageType; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.passport.identifier.PassportRadioId; import io.github.dsheirer.module.decode.passport.identifier.PassportTalkgroup; import io.github.dsheirer.protocol.Protocol; @@ -158,7 +159,8 @@ public class PassportDecoderState extends DecoderState } } - mCurrentDecodeEvent = DecodeEvent.builder(passport.getTimestamp()) + mCurrentDecodeEvent = PassportDecodeEvent.builder(passport.getTimestamp()) + .eventType(passport.getMessageType() == MessageType.CA_STRT ? DecodeEventType.CALL : DecodeEventType.DATA_CALL) .eventDescription(passport.getMessageType() == MessageType.CA_STRT ? "Call" : "Data") .identifiers(getIdentifierCollection().copyOf()) .details(passport.toString()) @@ -177,7 +179,8 @@ public class PassportDecoderState extends DecoderState !isSameTalkgroup(to, getToIdentifier(callDetect.getIdentifierCollection())) || callDetect.getTimeStart() < (passport.getTimestamp() - 45000)) { - callDetect = DecodeEvent.builder(passport.getTimestamp()) + callDetect = PassportDecodeEvent.builder(passport.getTimestamp()) + .eventType(DecodeEventType.CALL_DETECT) .eventDescription("Call Detect") .identifiers(new IdentifierCollection(passport.getIdentifiers())) // .channel(...) @@ -218,7 +221,8 @@ public class PassportDecoderState extends DecoderState } break; case RA_REGI: - broadcast(DecodeEvent.builder(passport.getTimestamp()) + broadcast(PassportDecodeEvent.builder(passport.getTimestamp()) + .eventType(DecodeEventType.REGISTER) .eventDescription("Register") .identifiers(new IdentifierCollection(passport.getIdentifiers())) .build()); diff --git a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderState.java b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderState.java index f0380ba0..4c26f35c 100644 --- a/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderState.java +++ b/src/main/java/io/github/dsheirer/module/decode/tait/Tait1200DecoderState.java @@ -30,14 +30,17 @@ import io.github.dsheirer.identifier.MutableIdentifierCollection; import io.github.dsheirer.message.IMessage; import io.github.dsheirer.module.decode.DecoderType; import io.github.dsheirer.module.decode.event.DecodeEvent; +import io.github.dsheirer.module.decode.event.DecodeEventType; import io.github.dsheirer.module.decode.event.PlottableDecodeEvent; import io.github.dsheirer.module.decode.tait.identifier.TaitIdentifier; +import io.github.dsheirer.protocol.Protocol; import org.jdesktop.swingx.mapviewer.GeoPosition; import java.util.TreeSet; public class Tait1200DecoderState extends DecoderState { + public static final Protocol PROTOCOL_TAIT_1200 = Protocol.TAIT1200; private TreeSet mIdents = new TreeSet<>(); public Tait1200DecoderState() @@ -95,6 +98,8 @@ public class Tait1200DecoderState extends DecoderState ic.update(message.getIdentifiers()); PlottableDecodeEvent event = PlottableDecodeEvent.plottableBuilder(gps.getTimestamp()) + .protocol(PROTOCOL_TAIT_1200) + .eventType(DecodeEventType.GPS) .eventDescription("GPS") .identifiers(ic) .location(position) @@ -117,6 +122,8 @@ public class Tait1200DecoderState extends DecoderState ic.update(message.getIdentifiers()); broadcast(DecodeEvent.builder(ani.getTimestamp()) + .protocol(PROTOCOL_TAIT_1200) + .eventType(DecodeEventType.ID_ANI) .eventDescription("ANI") .identifiers(ic) .details("Automatic Number Identification")