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")