#1368 Restructures Message and Event filter framework.

This commit is contained in:
Dennis Sheirer
2023-07-12 04:41:57 -04:00
parent d246ffff30
commit b0a524cf62
121 changed files with 4869 additions and 4950 deletions

View File

@@ -25,26 +25,21 @@ 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.*;
import java.awt.*;
import javax.swing.JPanel;
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;
private final ChannelMetadataPanel mChannelMetadataPanel;
private final ChannelDetailPanel mChannelDetailPanel;
private final DecodeEventPanel mDecodeEventPanel;
private final MessageActivityPanel mMessageActivityPanel;
private final ChannelPowerPanel mChannelPowerPanel;
/**
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
@@ -55,8 +50,6 @@ 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);
@@ -71,13 +64,15 @@ 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);
@@ -86,8 +81,6 @@ public class NowPlayingPanel extends JPanel
mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelDetailPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mDecodeEventPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mLastSeenPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mLastHeardPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mMessageActivityPanel);
mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelPowerPanel);
}

View File

@@ -1,35 +1,61 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
/**
* Implements a message filter that passes (true) all messages
* Implements an all-pass filter that automatically passes/allows any presented object.
*
* A key type of String is used, but that makes no difference in how this filter functions.
*/
@SuppressWarnings( "rawtypes" )
public class AllPassFilter<T> extends Filter<T>
public class AllPassFilter<T> extends Filter<T, String>
{
public AllPassFilter()
private static final String KEY = "Other/Unlisted";
private final AllPassKeyExtractor mKeyExtractor = new AllPassKeyExtractor();
/**
* Constructor
* @param name to display for this filter
*/
public AllPassFilter(String name)
{
super(name);
add(new FilterElement<>(KEY));
}
@Override
public Function getKeyExtractor()
{
super( "Allow All Messages" );
return mKeyExtractor;
}
@Override
public boolean passes( T t )
{
return true;
}
@Override
public boolean canProcess( T t )
{
return true;
}
@Override
@SuppressWarnings( "unchecked" )
public List getFilterElements()
{
return Collections.EMPTY_LIST;
}
/**
* Key extractor that always returns the same (String)key.
*/
public class AllPassKeyExtractor implements Function<T,String>
{
@Override
public String apply(T t)
{
return KEY;
}
}
}

View File

@@ -1,55 +1,206 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class Filter<T> implements IFilter<T>
/**
* Base filter class that uses an extractor function to extract a filter key (K) from an object of type T and then
* lookup the filter element (rule) indicating if objects of this type are enabled.
*
* @param <T> object type that this filter can process.
* @param <K> key type used to lookup filter elements
*/
public abstract class Filter<T,K> implements IFilter<T>
{
protected String mName;
protected boolean mEnabled = true;
public Filter( String name )
{
mName = name;
}
private static final Logger LOG = LoggerFactory.getLogger(Filter.class);
private String mName;
private Map<K, FilterElement<K>> mFilterElementMap = new HashMap<>();
private IFilterChangeListener mFilterChangeListener;
/**
* Indicates if this filter is enabled or disabled
*/
public boolean isEnabled()
{
return mEnabled;
}
/**
* Constructs an instance
* @param name of this filter
*/
public Filter(String name)
{
mName = name;
}
/**
* Enables (true) or disables (false) this filter. An enabled filter will
* evaluate messages and a disabled filter will return false on all message
* test methods.
*/
public void setEnabled( boolean enabled )
{
mEnabled = enabled;
}
/**
* List of filter elements managed by this filter
*/
public abstract List<FilterElement<?>> getFilterElements();
/**
* Indicates if this filter contains a filter element matching the key value.
* @param key to check
* @return true if this filter has a matching filter element.
*/
protected boolean hasKey(K key)
{
return mFilterElementMap.containsKey(key);
}
/**
* Name of this filter
*/
public String getName()
{
return mName;
}
public void setName( String name )
{
mName = name;
}
public String toString()
{
return mName;
}
/**
* Key extractor function provided by the subclass.
* @return key extractor function
*/
public abstract Function<T,K> getKeyExtractor();
@Override
public boolean passes(T t)
{
K key = getKeyExtractor().apply(t);
if(mFilterElementMap.containsKey(key))
{
return mFilterElementMap.get(key).isEnabled();
}
return false;
}
/**
* Indicates if this filter can process the item T, meaning that it has a FilterElement that matches the key
* value for items of type T.
* @param t to test
* @return true if this filter can process item t
*/
@Override
public boolean canProcess(T t)
{
return hasKey(getKeyExtractor().apply(t));
}
@Override
public boolean isEnabled()
{
for(FilterElement filterElement: mFilterElementMap.values())
{
if(filterElement.isEnabled())
{
return true;
}
}
return false;
}
/**
* Adds the child filter element to this filter
* @param filterElement to add
*/
public void add(FilterElement<K> filterElement)
{
if(mFilterElementMap.containsKey(filterElement.getElement()))
{
LOG.warn("Filter element for key [" + filterElement.getElement() + "] named [" + filterElement.getName() +
"] already exists - overwriting existing value");
}
mFilterElementMap.put(filterElement.getElement(), filterElement);
filterElement.register(mFilterChangeListener);
}
/**
* List of filter elements managed by this filter
*/
public final List<FilterElement<K>> getFilterElements()
{
return new ArrayList<>(mFilterElementMap.values());
}
/**
* Name of this filter
*/
public String getName()
{
return mName;
}
/**
* Sets the name of this filter
*
* @param name to set
*/
public void setName(String name)
{
mName = name;
}
/**
* Pretty version or name of this filter.
*
* @return name
*/
public String toString()
{
return mName;
}
/**
* Count of enabled child filter elements.
*
* @return count
*/
@Override
public int getEnabledCount()
{
int count = 0;
for(FilterElement filterElement : mFilterElementMap.values())
{
if(filterElement.isEnabled())
{
count++;
}
}
return count;
}
/**
* Count of child filter elements
*
* @return count
*/
@Override
public int getElementCount()
{
return mFilterElementMap.size();
}
/**
* Registers a filter change listener on this filter and all filter elements.
* @param listener to be notified of any filter changes.
*/
@Override
public void register(IFilterChangeListener listener)
{
mFilterChangeListener = listener;
for(FilterElement child: mFilterElementMap.values())
{
child.register(listener);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import java.awt.Component;
import net.miginfocom.swing.MigLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
/**
* Filter editor
* @param <T> item type for editing
*/
public class FilterEditor<T> extends JFrame
{
private FilterEditorPanel<T> mEditorPanel;
/**
* Constructor
* @param title for the editor window frame
* @param owner to register the popup location
* @param filterSet to use initially
*/
public FilterEditor(String title, Component owner, FilterSet<T> filterSet)
{
if(filterSet == null)
{
throw new IllegalArgumentException("Unable to construct FilterEditor - FilterSet cannot be null");
}
setTitle(title);
setSize(600, 400);
setLocationRelativeTo(owner);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][]"));
mEditorPanel = new FilterEditorPanel<>(filterSet);
JScrollPane scroller = new JScrollPane(mEditorPanel);
scroller.setViewportView(mEditorPanel);
add(scroller, "wrap");
JButton close = new JButton("Close");
close.addActionListener(e -> dispose());
add(close);
}
/**
* Updates this editor with the filterset.
* @param filterSet to use in this editor.
*/
public void updateFilterSet(FilterSet<T> filterSet)
{
mEditorPanel.updateFilterSet(filterSet);
}
}

View File

@@ -1,100 +1,128 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import java.awt.Component;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.CellEditorListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreeSelectionModel;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.List;
/**
* Editor panel for managing the state of a filter set.
*
* @param <T> element type for the filter set.
*/
public class FilterEditorPanel<T> extends JPanel
{
private static final long serialVersionUID = 1L;
private JTree mTree;
private DefaultTreeModel mModel;
private FilterSet<T> mFilterSet;
/**
* Constructs an instance
* @param filterSet to manage
*/
public FilterEditorPanel(FilterSet<T> filterSet)
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
mFilterSet = filterSet;
init();
}
/**
* Initializes the panel and the tree model.
*/
private void init()
{
DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet);
mModel = new DefaultTreeModel(root);
addFilterSet(mFilterSet, root);
mTree = new JTree(mModel);
mTree.setShowsRootHandles(true);
mTree.addMouseListener(new MouseHandler());
mTree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
mTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
mTree.setCellRenderer(new EditorTreeCellRenderer());
mTree.setCellEditor(new FilterTreeNodeEditor());
mTree.setCellEditor(new FilterTreeCellEditor());
mTree.setEditable(true);
add(mTree);
}
/**
* Updates the filter set for this editor panel
* @param filterSet to use
*/
public void updateFilterSet(FilterSet<T> filterSet)
{
remove(mTree);
mFilterSet = filterSet;
init();
revalidate();
}
/**
* Adds the filter set as a child tree node to the parent.
* @param filterSet to add to the tree
* @param parent node for the filter set child tree node.
*/
private void addFilterSet(FilterSet<T> filterSet, DefaultMutableTreeNode parent)
{
List<IFilter<T>> filters = filterSet.getFilters();
/* sort the filters in alphabetical order by name */
filters.sort(Comparator.comparing(IFilter::getName));
for(IFilter<T> filter : filters)
{
DefaultMutableTreeNode child = new DefaultMutableTreeNode(filter);
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(filter);
mModel.insertNodeInto(childNode, parent, parent.getChildCount());
mModel.insertNodeInto(child, parent, parent.getChildCount());
if(filter instanceof FilterSet)
if(filter instanceof FilterSet childFilterSet)
{
addFilterSet((FilterSet<T>) filter, child);
addFilterSet(childFilterSet, childNode);
}
else if(filter instanceof Filter)
else if(filter instanceof Filter childFilter)
{
addFilter((Filter<T>) filter, child);
addFilter(childFilter, childNode);
}
}
}
private void addFilter(Filter<T> filter, DefaultMutableTreeNode parent)
/**
* Adds the filter as a child tree node to the parent.
*
* @param filter to add
* @param parent tree node
*/
private void addFilter(Filter filter, DefaultMutableTreeNode parent)
{
List<FilterElement<?>> elements = filter.getFilterElements();
@@ -103,123 +131,39 @@ public class FilterEditorPanel<T> extends JPanel
for(FilterElement<?> element : elements)
{
DefaultMutableTreeNode child = new DefaultMutableTreeNode(element);
mModel.insertNodeInto(child, parent, parent.getChildCount());
}
}
public class MouseHandler implements MouseListener
/**
* Accesses the tree's root tree node.
* @return root node.
*/
private DefaultMutableTreeNode getRoot()
{
public MouseHandler()
if(mTree.getModel().getRoot() instanceof DefaultMutableTreeNode root)
{
return root;
}
@Override
public void mouseClicked(MouseEvent event)
{
if(SwingUtilities.isRightMouseButton(event))
{
int row = mTree.getRowForLocation(event.getX(),
event.getY());
if(row != -1)
{
mTree.setSelectionRow(row);
Object selectedNode =
mTree.getLastSelectedPathComponent();
if(selectedNode instanceof DefaultMutableTreeNode)
{
final DefaultMutableTreeNode node =
(DefaultMutableTreeNode) selectedNode;
if(node.getChildCount() > 0)
{
JPopupMenu selectionMenu = new JPopupMenu();
JMenuItem selectAll = new JMenuItem("Select All");
selectAll.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
setFilterEnabled(mModel, node, true);
}
});
selectionMenu.add(selectAll);
JMenuItem deselectAll = new JMenuItem("Deselect All");
deselectAll.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
setFilterEnabled(mModel, node, false);
}
});
selectionMenu.add(deselectAll);
selectionMenu.show(mTree,
event.getX(),
event.getY());
}
}
}
}
}
@Override
public void mousePressed(MouseEvent e)
{
}
@Override
public void mouseReleased(MouseEvent e)
{
}
@Override
public void mouseEntered(MouseEvent e)
{
}
@Override
public void mouseExited(MouseEvent e)
{
}
return null;
}
/**
* Recursively sets the enabled state on all filter node children below the
* specified parent node.
* Recursively sets the enabled state on all filter node children below the specified parent node.
*
* @param model - model containing the tree nodes
* @param node - parent node
* @param enabled - true or false to enable or disable the filters
*/
private void setFilterEnabled(DefaultTreeModel model,
DefaultMutableTreeNode node,
boolean enabled)
private void setFilterEnabled(DefaultTreeModel model, DefaultMutableTreeNode node, boolean enabled)
{
Object obj = node.getUserObject();
if(obj instanceof FilterSet<?>)
if(obj instanceof FilterElement filterElement)
{
((FilterSet<?>) obj).setEnabled(enabled);
filterElement.setEnabled(enabled);
}
else if(obj instanceof Filter)
{
((Filter<?>) obj).setEnabled(enabled);
}
else if(obj instanceof FilterElement)
{
((FilterElement<?>) obj).setEnabled(enabled);
}
model.nodeChanged(node);
/* Recursively set the children of this node */
Enumeration<?> children = node.children();
@@ -228,43 +172,123 @@ public class FilterEditorPanel<T> extends JPanel
{
Object child = children.nextElement();
if(child instanceof DefaultMutableTreeNode)
if(child instanceof DefaultMutableTreeNode childNode)
{
setFilterEnabled(model, (DefaultMutableTreeNode) child, enabled);
setFilterEnabled(model, childNode, enabled);
}
}
model.nodeChanged(node);
}
/**
* Recursively searches the tree to find the tree node containing the specified filter element.
*
* @param node to start search
* @param element to find
*/
private DefaultMutableTreeNode findFilterElementNode(DefaultMutableTreeNode node, FilterElement<?> element)
{
if(node != null)
{
if(node.getUserObject() instanceof FilterElement<?> selfFilterElement && selfFilterElement.equals(element))
{
return node;
}
for(int x = 0; x < node.getChildCount(); x++)
{
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
{
DefaultMutableTreeNode found = findFilterElementNode(child, element);
if(found != null)
{
return found;
}
}
}
}
return null;
}
/**
* Updates the display value for the specified node and all parents in the nodes parentage.
* @param node to update
*/
private void updateParentage(DefaultMutableTreeNode node)
{
mModel.nodeChanged(node);
TreeNode parent = node.getParent();
while(parent != null)
{
mModel.nodeChanged(parent);
parent = parent.getParent();
}
}
/**
* Recursively searches the tree to find the tree node containing the specified filter.
*
* @param node to start search
* @param filter to find
*/
private DefaultMutableTreeNode findFilterNode(DefaultMutableTreeNode node, IFilter<?> filter)
{
if(node != null)
{
if(node.getUserObject() instanceof IFilter<?> selfFilter && selfFilter.equals(filter))
{
return node;
}
for(int x = 0; x < node.getChildCount(); x++)
{
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
{
DefaultMutableTreeNode found = findFilterNode(child, filter);
if(found != null)
{
return found;
}
}
}
}
else
{
System.out.println("Can't evaluate - node is null");
}
return null;
}
/**
* Custom cell renderer
*/
public class EditorTreeCellRenderer extends DefaultTreeCellRenderer
{
private static final long serialVersionUID = 1L;
public Component getTreeCellRendererComponent(JTree tree,
Object treeNode,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus)
public Component getTreeCellRendererComponent(JTree tree, Object treeNode, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus)
{
if(treeNode instanceof DefaultMutableTreeNode)
{
Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject();
JCheckBox checkBox = null;
if(userObject instanceof IFilter)
if(userObject instanceof IFilter filter)
{
IFilter<?> filter = (IFilter<?>) userObject;
checkBox = new JCheckBox(filter.getName());
checkBox = new FilterCheckBox(filter);
checkBox.setSelected(filter.isEnabled());
}
else if(userObject instanceof FilterElement)
else if(userObject instanceof FilterElement element)
{
FilterElement<?> element = (FilterElement<?>) userObject;
checkBox = new JCheckBox(element.getName());
checkBox = new FilterElementCheckBox(element);
checkBox.setSelected(element.isEnabled());
}
@@ -285,95 +309,110 @@ public class FilterEditorPanel<T> extends JPanel
}
}
return super.getTreeCellRendererComponent(tree, treeNode, selected,
expanded, leaf, row, hasFocus);
return super.getTreeCellRendererComponent(tree, treeNode, selected, expanded, leaf, row, hasFocus);
}
}
/**
* Check box used in filter tree nodes
*/
public class FilterCheckBox extends JCheckBox
{
private static final long serialVersionUID = 1L;
private IFilter<?> mFilter;
private IFilter<T> mFilter;
public FilterCheckBox(IFilter<T> filter)
/**
* Constructs an instance
*
* @param filter
*/
public FilterCheckBox(IFilter<?> filter)
{
super(filter.getName());
mFilter = filter;
setSelected(mFilter.isEnabled());
addItemListener(e -> {
DefaultMutableTreeNode selfNode = findFilterNode(getRoot(), mFilter);
addItemListener(new ItemListener()
{
@Override
public void itemStateChanged(ItemEvent e)
if(selfNode != null)
{
mFilter.setEnabled(FilterCheckBox.this.isSelected());
setFilterEnabled(mModel, selfNode, FilterCheckBox.this.isSelected());
updateLabel();
updateParentage(selfNode);
}
});
}
@Override
public void setSelected(boolean b)
{
super.setSelected(b);
updateLabel();
}
public void updateLabel()
{
setText(mFilter.getName() + " (" + mFilter.getEnabledCount() + "/" + mFilter.getElementCount() + ")");
}
}
/**
* Check box used in filter element tree nodes
*/
public class FilterElementCheckBox extends JCheckBox
{
private static final long serialVersionUID = 1L;
private FilterElement<?> mFilter;
/**
* Constructs an instance
*
* @param filter element
*/
public FilterElementCheckBox(FilterElement<?> filter)
{
super(filter.getName());
mFilter = filter;
setSelected(mFilter.isEnabled());
addItemListener(new ItemListener()
{
@Override
public void itemStateChanged(ItemEvent arg0)
addItemListener(arg0 -> {
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
DefaultMutableTreeNode self = findFilterElementNode(getRoot(), mFilter);
if(self != null)
{
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
updateParentage(self);
}
});
}
}
public class FilterTreeNodeEditor implements TreeCellEditor
/**
* Editor for filter tree nodes
*/
public class FilterTreeCellEditor implements TreeCellEditor
{
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Component getTreeCellEditorComponent(JTree tree, Object node,
boolean isSelected, boolean expanded, boolean leaf, int row)
public Component getTreeCellEditorComponent(JTree tree, Object node, boolean isSelected, boolean expanded, boolean leaf, int row)
{
if(node instanceof DefaultMutableTreeNode)
if(node instanceof DefaultMutableTreeNode mutableTreeNode)
{
Object userObject = ((DefaultMutableTreeNode) node).getUserObject();
Object userObject = mutableTreeNode.getUserObject();
if(userObject instanceof IFilter)
if(userObject instanceof IFilter filter)
{
return new FilterCheckBox((IFilter) userObject);
return new FilterCheckBox(filter);
}
else if(userObject instanceof FilterElement)
else if(userObject instanceof FilterElement filterElement)
{
return new FilterElementCheckBox((FilterElement<?>) userObject);
return new FilterElementCheckBox(filterElement);
}
}
return new JLabel(node.toString());
}
@Override
public void addCellEditorListener(CellEditorListener l)
{
}
public void addCellEditorListener(CellEditorListener l) {}
@Override
public void cancelCellEditing()
{
}
public void cancelCellEditing() {}
@Override
public Object getCellEditorValue()
@@ -388,9 +427,7 @@ public class FilterEditorPanel<T> extends JPanel
}
@Override
public void removeCellEditorListener(CellEditorListener l)
{
}
public void removeCellEditorListener(CellEditorListener l) {}
@Override
public boolean shouldSelectCell(EventObject anEvent)

View File

@@ -1,55 +1,148 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
/**
* Filter element provides filtering and enabled state tracking for an individual filterable element.
*
* @param <T> element to track and filter.
*/
public class FilterElement<T> implements Comparable<FilterElement<T>>
{
private boolean mEnabled;
private T mElement;
public FilterElement( T element, boolean enabled )
{
mElement = element;
mEnabled = enabled;
}
public FilterElement( T t )
{
this( t, true );
}
public T getElement()
{
return mElement;
}
public String toString()
{
return getName();
}
public boolean isEnabled()
{
return mEnabled;
}
public void setEnabled( boolean enabled )
{
mEnabled = enabled;
}
public String getName()
{
return mElement.toString();
}
private boolean mEnabled;
private final T mElement;
private IFilterChangeListener mFilterChangeListener;
@Override
public int compareTo( FilterElement<T> other )
/**
* Constructs an instance
*
* @param element to track
* @param enabled enabled state of this filter element
*/
public FilterElement(T element, boolean enabled)
{
return toString().compareTo( other.toString() );
mElement = element;
mEnabled = enabled;
}
/**
* Constructs an instance with a default state of enabled (true).
*
* @param t element to track
*/
public FilterElement(T t)
{
this(t, true);
}
/**
* Registers the listener with each child filter element.
*
* @param listener to register
*/
public void register(IFilterChangeListener listener)
{
mFilterChangeListener = listener;
}
/**
* Element being filtered.
*
* @return element
*/
public T getElement()
{
return mElement;
}
/**
* Name or display string for the element.
*
* @return string version of the element.
*/
public String toString()
{
return getName();
}
/**
* Indicates if this filter is enabled indicating that elements of this type will pass this filter.
*
* @return enabled state.
*/
public boolean isEnabled()
{
return mEnabled;
}
/**
* Sets the enabled state of this filter.
*
* @param enabled true to allow this filter's element type to pass the filter.
*/
public void setEnabled(boolean enabled)
{
mEnabled = enabled;
if(mFilterChangeListener != null)
{
mFilterChangeListener.filterChanged();
}
}
/**
* Name of the tracked element.
*
* @return name
*/
public String getName()
{
return mElement.toString();
}
/**
* Implements compareto interface for ordering of filter elements.
*
* @param other the object to be compared.
* @return comparison value
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof FilterElement)) return false;
return compareTo((FilterElement) o) == 0;
public int compareTo(FilterElement<T> other)
{
return toString().compareTo(other.toString());
}
/**
* Implement equals interface for comparison of filter elements.
*
* @param o to compare
* @return comparison
*/
@Override
public boolean equals(Object o)
{
if(o instanceof FilterElement fe)
{
return compareTo(fe) == 0;
}
return false;
}
}

View File

@@ -1,108 +1,239 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import io.github.dsheirer.log.LoggingSuppressor;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Filter set is a parent container for filters.
*
* @param <T> type of element tracked/filtered by this filter set.
*/
public class FilterSet<T> implements IFilter<T>
{
protected List<IFilter<T>> mFilters = new ArrayList<IFilter<T>>();
protected boolean mEnabled = true;
protected String mName;
private static final Logger LOGGER = LoggerFactory.getLogger(FilterSet.class);
private static LoggingSuppressor sLoggingSuppressor = new LoggingSuppressor(LOGGER);
private List<IFilter<T>> mFilters = new ArrayList<>();
private String mName;
private IFilterChangeListener mFilterChangeListener;
public FilterSet( String name )
{
mName = name;
}
public FilterSet()
{
mName = "Message Filter";
}
public FilterSet(IFilter<T> filter)
/**
* Constructs an instance of a filter set.
*
* @param name of this filter set.
*/
public FilterSet(String name)
{
mName = name;
}
/**
* Constructs an instance with a single filter using the default 'Filters' name.
*
* @param filter for this filter set.
*/
public FilterSet(IFilter<T> filter)
{
this("Filters");
addFilter(filter);
}
public String getName()
{
return mName;
}
public void setName( String name )
{
mName = name;
}
public String toString()
{
return getName();
}
@Override
public boolean passes( T t )
{
if( mEnabled )
{
for( IFilter<T> filter: mFilters )
{
// Make sure to loop through all filters.
// Only return if true or all filters have been evaluated.
if( filter.canProcess( t ) && filter.passes( t ))
{
return true;
}
}
}
return false;
}
@Override
public boolean canProcess( T t )
{
if( mEnabled )
/**
* Registers the listener with each child filter element.
* @param listener to register
*/
public void register(IFilterChangeListener listener)
{
mFilterChangeListener = listener;
for(IFilter child: mFilters)
{
for( IFilter<T> filter: mFilters )
child.register(listener);
}
}
/**
* Name of this filter set.
*
* @return name
*/
public String getName()
{
return mName;
}
/**
* Sets the name of this filter set.
*
* @param name to set
*/
public void setName(String name)
{
mName = name;
}
/**
* Name for this filter set.
*
* @return name
*/
public String toString()
{
return getName();
}
/**
* Indicates if the element passes at least one of the child filters in this filter set.
*
* @param t to evaluate
* @return true if the argument passes a child filter.
*/
@Override
public boolean passes(T t)
{
for(IFilter<T> filter : mFilters)
{
//Stop at the first filter that says that it can process the message type.
if(filter.canProcess(t))
{
if( filter.canProcess( t ) )
{
return true;
}
return filter.passes(t);
}
}
return false;
}
}
/**
* Indicates if this filter set contains at least one filter that can evaluate the type of argument that is presented.
*
* @param t element to evaluate
* @return true if this filter set can evaluate objects of the argument's type.
*/
@Override
public boolean canProcess(T t)
{
for(IFilter<T> filter : mFilters)
{
if(filter.canProcess(t))
{
return true;
}
}
sLoggingSuppressor.info(t.getClass().getSimpleName(), 1, "FilterSet [" +
this.getClass().getSimpleName() + "] has no filter element for items of type [" +
t.getClass().getSimpleName() + "]");
return false;
}
/**
* Child filters
*
* @return filters
*/
public List<IFilter<T>> getFilters()
{
return mFilters;
}
public void addFilters( List<IFilter<T>> filters )
{
mFilters.addAll( filters );
}
public void addFilter( IFilter<T> filter )
{
mFilters.add( filter );
}
public void removeFilter( IFilter<T> filter )
{
mFilters.remove( filter );
return new ArrayList<>(mFilters);
}
@Override
public boolean isEnabled()
{
return mEnabled;
}
/**
* Adds a list of child filters to this filter set.
* @param filters to add
*/
public void addFilters(List<IFilter<T>> filters)
{
for(IFilter<T> filter: filters)
{
addFilter(filter);
}
}
/**
* Adds the child filter to this filter set.
* @param filter to add
*/
public void addFilter(IFilter<T> filter)
{
if(filter instanceof Filter<?,?> filterInstance && filterInstance.getKeyExtractor() == null)
{
LOGGER.warn("Filter [" + filter.getClass() + "] has a null key extractor");
}
mFilters.add(filter);
filter.register(mFilterChangeListener);
}
/**
* Indicates if any of the child filters contain at least one filter element that is enabled.
* @return enabled state
*/
@Override
public void setEnabled( boolean enabled )
{
mEnabled = enabled;
}
public boolean isEnabled()
{
for(IFilter filter: mFilters)
{
if(filter.isEnabled())
{
return true;
}
}
return false;
}
/**
* Recursive count of enabled child filter elements.
* @return count
*/
@Override
public int getEnabledCount()
{
int count = 0;
for(IFilter filter: mFilters)
{
count += filter.getEnabledCount();
}
return count;
}
/**
* Recursive count of child filter elements.
* @return count
*/
@Override
public int getElementCount()
{
int count = 0;
for(IFilter filter: mFilters)
{
count += filter.getElementCount();
}
return count;
}
}

View File

@@ -1,36 +1,75 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
/**
* Filter interface
*
* @param <T> type of filter
*/
public interface IFilter<T>
{
/**
* Generic filter method.
*
* @param message - message to filter
* @return - true if the message passes the filter
*/
public boolean passes( T t );
/**
* Generic filter method.
*
* @param t - message to filter
* @return - true if the message passes the filter
*/
boolean passes(T t);
/**
* Indicates if the filter can process (filter) the object
*
* @param message - candidate message for filtering
*
* @return - true if the filter is capable of filtering the message
*/
public boolean canProcess( T t );
/**
* Indicates if the filter can process (filter) the object
*
* @param t - candidate message for filtering
* @return - true if the filter is capable of filtering the message
*/
boolean canProcess(T t);
/**
* Indicates if this filter is enabled to evaluate messages
*/
public boolean isEnabled();
/**
* Indicates if this filter is enabled to evaluate messages
*/
boolean isEnabled();
/**
* Sets the enabled state of the filter
*/
public void setEnabled( boolean enabled );
/**
* Display name for the filter
*/
public String getName();
/**
* Display name for the filter
*/
String getName();
/**
* (Recursive) count of enabled child filter elements.
*
* @return total enable count.
*/
int getEnabledCount();
/**
* (Recursive) count of total child filter elements.
*
* @return total count of child filter elements.
*/
int getElementCount();
/**
* Registers the listener to be notified when the enabled state of any child filter elements is updated.
*
* @param listener to be notified
*/
void register(IFilterChangeListener listener);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,19 +17,9 @@
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter.lastseen;
package io.github.dsheirer.filter;
import io.github.dsheirer.module.ModuleEventBusMessage;
/**
* Request for decode event history from a processing chain for the specified channel.
*/
public class LastSeenHistoryRequest extends ModuleEventBusMessage
public interface IFilterChangeListener
{
/**
* Constructs an instance
*/
public LastSeenHistoryRequest()
{
}
void filterChanged();
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,30 +14,36 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
package io.github.dsheirer.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.SyncLossMessage;
import java.util.function.Function;
import java.util.Collections;
import java.util.List;
public class SyncLossMessageFilter extends Filter<IMessage>
/**
* Filter for sync-loss messages
*/
public class SyncLossMessageFilter extends Filter<IMessage,String>
{
private static final String SYNC_LOSS_KEY = "Sync-Loss";
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public SyncLossMessageFilter()
{
super("Sync Loss");
super("Sync Loss Messages");
add(new FilterElement<>(SYNC_LOSS_KEY));
}
@Override
public boolean passes(IMessage message)
public Function<IMessage, String> getKeyExtractor()
{
return mEnabled && canProcess(message);
return mKeyExtractor;
}
@Override
@@ -47,9 +52,20 @@ public class SyncLossMessageFilter extends Filter<IMessage>
return message instanceof SyncLossMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
return Collections.EMPTY_LIST;
@Override
public String apply(IMessage message)
{
if(message instanceof SyncLossMessage)
{
return SYNC_LOSS_KEY;
}
return null;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,7 +21,6 @@ package io.github.dsheirer.module;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.List;
@@ -50,7 +49,7 @@ public abstract class HistoryModule<T> extends Module implements Listener<T>
*/
public List<T> getItems()
{
return new ArrayList<>(mItems);
return new ArrayList<>(mItems);
}
@Override

View File

@@ -117,8 +117,8 @@ public class ProcessingChain implements Listener<ChannelEvent>
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
private MessageHistory mMessageHistory = new MessageHistory(500);
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(200);
private MessageHistory mMessageHistory = new MessageHistory(200);
private AbstractChannelState mChannelState;
private EventBus mEventBus;
protected Source mSource;

View File

@@ -47,6 +47,7 @@ import io.github.dsheirer.module.decode.dmr.DMRDecoderState;
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet;
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState;
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
@@ -80,16 +81,18 @@ import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FM;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSM;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState;
import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25MessageFilterSet;
import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25P1MessageFilterSet;
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK;
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
import io.github.dsheirer.module.decode.p25.phase2.message.filter.P25P2MessageFilterSet;
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
import io.github.dsheirer.module.decode.passport.PassportDecoder;
import io.github.dsheirer.module.decode.passport.PassportDecoderState;
import io.github.dsheirer.module.decode.passport.PassportMessageFilter;
import io.github.dsheirer.module.decode.tait.Tait1200Decoder;
import io.github.dsheirer.module.decode.tait.Tait1200DecoderState;
import io.github.dsheirer.module.decode.tait.Tait1200MessageFilter;
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule;
import io.github.dsheirer.preference.UserPreferences;
@@ -494,7 +497,7 @@ public class DecoderFactory
*/
public static FilterSet<IMessage> getMessageFilters(List<Module> modules)
{
FilterSet<IMessage> filterSet = new FilterSet<>();
FilterSet<IMessage> filterSet = new FilterSet<>("Message Filters");
for(Module module : modules)
{
@@ -504,28 +507,28 @@ public class DecoderFactory
}
}
/* If we don't have any filters, add an ALL-PASS filter */
if(filterSet.getFilters().isEmpty())
{
filterSet.addFilter(new AllPassFilter<>());
}
//Add an all-others filter as a catch-all for anything that isn't handled by the decoder filters.
filterSet.addFilter(new AllPassFilter<>("All Other Messages Filter"));
return filterSet;
}
/**
* Returns a set of IMessageFilter objects (FilterSets or Filters) that
* can process all of the messages produced by the specified decoder type.
* can process all messages produced by the specified decoder type.
*/
public static List<IFilter<IMessage>> getMessageFilter(DecoderType decoder)
{
ArrayList<IFilter<IMessage>> filters = new ArrayList<>();
List<IFilter<IMessage>> filters = new ArrayList<>();
switch(decoder)
{
case DCS:
filters.add(new DCSMessageFilter());
break;
case DMR:
filters.add(new DmrMessageFilterSet());
break;
case FLEETSYNC2:
filters.add(new FleetsyncMessageFilter());
break;
@@ -545,13 +548,16 @@ public class DecoderFactory
filters.add(new MPT1327MessageFilter());
break;
case P25_PHASE1:
filters.add(new P25MessageFilterSet());
filters.add(new P25P1MessageFilterSet());
break;
case P25_PHASE2:
filters.add(new P25P2MessageFilterSet());
break;
case PASSPORT:
filters.add(new PassportMessageFilter());
break;
case DMR:
//filters.add(new DMR) //todo: not finished
case TAIT_1200:
filters.add(new Tait1200MessageFilter());
break;
default:
break;

View File

@@ -26,6 +26,7 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.p25.identifier.channel.StandardChannel;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventListener;
@@ -100,9 +101,8 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
if(mDecodeEvent == null)
{
mDecodeEvent = DecodeEvent.builder(System.currentTimeMillis())
mDecodeEvent = DecodeEvent.builder(DecodeEventType.CALL, System.currentTimeMillis())
.channel(mChannelDescriptor)
.eventDescription("CALL")
.details(getDecoderType().name())
.identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers()))
.build();

View File

@@ -22,45 +22,55 @@ package io.github.dsheirer.module.decode.dcs;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* Message filter for Digital Coded Squelch (DCS)
*/
public class DCSMessageFilter extends Filter<IMessage>
public class DCSMessageFilter extends Filter<IMessage,String>
{
private Map<String,FilterElement<String>> mElements = new HashMap<>();
private static final String DCS_MESSAGE_FILTER_TAG = "DCS";
private static final String DCS_KEY = "DCS";
private final KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public DCSMessageFilter()
{
super("DCS Message Filter");
mElements.put(DCS_MESSAGE_FILTER_TAG, new FilterElement<>("DCS Code", true));
}
@Override
public boolean passes(IMessage message)
{
if(mEnabled && canProcess(message))
{
return mElements.get(DCS_MESSAGE_FILTER_TAG).isEnabled();
}
return false;
super("DCS Messages");
add(new FilterElement(DCS_KEY));
}
/**
* Indicates that this filter can process DCS messages.
* @param message to test
* @return true if the message can be processed
*/
@Override
public boolean canProcess(IMessage message)
{
return message instanceof DCSMessage;
return message instanceof DCSMessage && super.canProcess(message);
}
/**
* Key extractor that always returns the same key constant.
* @return extractor function.
*/
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, String> getKeyExtractor()
{
return new ArrayList<FilterElement<?>>(mElements.values());
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
@Override
public String apply(IMessage message)
{
return DCS_KEY;
}
}
}

View File

@@ -286,9 +286,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
DecodeEvent smsEvent = DMRDecodeEvent.builder(sms.getTimestamp())
.eventType(DecodeEventType.SMS)
.eventDescription(DecodeEventType.SMS.toString())
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
.details("MESSAGE: " + sms.getSMS())
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
.timeslot(sms.getTimeslot())
@@ -312,9 +310,7 @@ public class DMRDecoderState extends TimeslotDecoderState
mic.update(hyteraSmsPacket.getSource());
mic.update(hyteraSmsPacket.getDestination());
DecodeEvent smsEvent = DMRDecodeEvent.builder(packet.getTimestamp())
.eventDescription(DecodeEventType.SMS.name())
.eventType(DecodeEventType.DATA_PACKET)
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
.identifiers(mic)
.timeslot(packet.getTimeslot())
.details("SMS:" + hyteraSmsPacket.getSMS())
@@ -326,9 +322,7 @@ public class DMRDecoderState extends TimeslotDecoderState
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(packet.getTimestamp())
.eventDescription(DecodeEventType.UNKNOWN_PACKET.name())
.eventType(DecodeEventType.DATA_PACKET)
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
.identifiers(mic)
.timeslot(packet.getTimeslot())
.details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
@@ -337,9 +331,7 @@ public class DMRDecoderState extends TimeslotDecoderState
}
else
{
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
.eventDescription(DecodeEventType.DATA_PACKET.name())
.eventType(DecodeEventType.DATA_PACKET)
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
.timeslot(packet.getTimeslot())
.details(packet.toString())
@@ -350,9 +342,8 @@ public class DMRDecoderState extends TimeslotDecoderState
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
if (geoPosition != null) {
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp())
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.GPS.toString())
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
.protocol(Protocol.LRRP)
.location(geoPosition)
@@ -784,9 +775,8 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
DecodeEvent event = DMRDecodeEvent.builder(csbk.getTimestamp())
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
.details("Registration Request")
.eventDescription(DecodeEventType.REGISTER.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.build();
@@ -794,9 +784,8 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
DecodeEvent regRespEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
.details("Registration Response")
.eventDescription(DecodeEventType.REGISTER.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.build();
@@ -837,9 +826,8 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
.details("TALKGROUP AFFILIATION")
.eventDescription(DecodeEventType.AFFILIATE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.build();
@@ -853,20 +841,16 @@ public class DMRDecoderState extends TimeslotDecoderState
}
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel, IdentifierCollection mergedIdentifiers) {
return DMRDecodeEvent.builder(csbk.getTimestamp())
return DMRDecodeEvent.builder(decodeEventType, 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())
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(details)
@@ -1119,8 +1103,7 @@ public class DMRDecoderState extends TimeslotDecoderState
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
ic.update(gpsInformation.getGPSLocation());
DecodeEvent gpsEvent = DMRDecodeEvent.builder(message.getTimestamp())
.eventDescription(DecodeEventType.GPS.toString())
DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp())
.identifiers(ic)
.timeslot(message.getTimeslot())
.details("LOCATION:" + gpsInformation.getGPSLocation())
@@ -1151,9 +1134,8 @@ public class DMRDecoderState extends TimeslotDecoderState
if(mCurrentCallEvent == null)
{
mCurrentCallEvent = DMRDecodeEvent.builder(timestamp)
mCurrentCallEvent = DMRDecodeEvent.builder(type, timestamp)
.channel(getCurrentChannel())
.eventDescription(type.toString())
.details(details)
.identifiers(getIdentifierCollection().copyOf())
.build();
@@ -1164,7 +1146,6 @@ public class DMRDecoderState extends TimeslotDecoderState
{
if(type != DecodeEventType.CALL)
{
mCurrentCallEvent.setEventDescription(type.toString());
mCurrentCallEvent.setDetails(details);
}

View File

@@ -329,10 +329,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
if(isStale(event, timestamp, identifierCollection)) //Create new event
{
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
.channel(channel)
.eventType(decodeEventType)
.eventDescription(decodeEventType.toString())
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.identifiers(identifierCollection)
.build();
@@ -351,11 +349,9 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
event.end(timestamp);
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
.channel(channel)
.eventType(decodeEventType)
.eventDescription(decodeEventType.toString() + " - Continue")
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.identifiers(identifierCollection)
.build();
@@ -392,15 +388,6 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
{
if(event.getEventDescription() == null)
{
event.setEventDescription(decodeEventType + IGNORED);
}
else if(!event.getEventDescription().endsWith(IGNORED))
{
event.setEventDescription(event.getEventDescription() + IGNORED);
}
if(event.getDetails() == null)
{
event.setDetails(DATA_CALL_IGNORED);

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.event;
@@ -34,11 +33,12 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
/**
* Constructs an instance
* @param decodeEventType for the event
* @param timestamp for the event
*/
public DMRChannelGrantEvent(long timestamp)
public DMRChannelGrantEvent(DecodeEventType decodeEventType, long timestamp)
{
super(timestamp);
super(decodeEventType, timestamp);
}
/**
@@ -63,9 +63,9 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(long timeStart)
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(DecodeEventType decodeEventType, long timeStart)
{
return new DMRChannelGrantDecodeEventBuilder(timeStart);
return new DMRChannelGrantDecodeEventBuilder(decodeEventType, timeStart);
}
/**
@@ -76,7 +76,6 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
protected long mTimeStart;
protected long mDuration;
protected DecodeEventType mDecodeEventType;
protected String mEventDescription;
protected IdentifierCollection mIdentifierCollection;
protected DMRChannel mChannel;
protected String mDetails;
@@ -87,8 +86,9 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
*
* @param timeStart
*/
public DMRChannelGrantDecodeEventBuilder(long timeStart)
public DMRChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
{
mDecodeEventType = decodeEventType;
mTimeStart = timeStart;
}
@@ -131,25 +131,6 @@ 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
*/
public DMRChannelGrantDecodeEventBuilder eventDescription(String description)
{
mEventDescription = description;
return this;
}
/**
* Sets the identifier collection.
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
@@ -176,13 +157,11 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
*/
public DMRChannelGrantEvent build()
{
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mTimeStart);
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mDecodeEventType, mTimeStart);
decodeEvent.setChannelDescriptor(mChannel);
decodeEvent.setTimeslot(mChannel.getTimeslot());
decodeEvent.setDetails(mDetails);
decodeEvent.setDuration(mDuration);
decodeEvent.setEventType(mDecodeEventType);
decodeEvent.setEventDescription(mEventDescription);
decodeEvent.setIdentifierCollection(mIdentifierCollection);
return decodeEvent;
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +14,13 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.event;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.protocol.Protocol;
/**
@@ -32,9 +32,9 @@ public class DMRDecodeEvent extends DecodeEvent
* Constucts a decode event
* @param start
*/
public DMRDecodeEvent(long start)
public DMRDecodeEvent(DecodeEventType decodeEventType, long start)
{
super(start);
super(decodeEventType, start);
setProtocol(Protocol.DMR);
}
@@ -43,9 +43,9 @@ public class DMRDecodeEvent extends DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
{
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
decodeEventBuilder.protocol(Protocol.DMR);
return decodeEventBuilder;
}

View File

@@ -39,7 +39,7 @@ public enum Opcode
STANDARD_CHANNEL_TIMING(Vendor.STANDARD, 7, "CHANNEL TIMING"),
STANDARD_ALOHA(Vendor.STANDARD, 25, "ALOHA"),
STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER(Vendor.STANDARD, 26, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT INBOUND HEADER"),
STANDARD_AHOY(Vendor.STANDARD, 28, "AHOY"),
STANDARD_ACTIVATION(Vendor.STANDARD, 30, "ACTIVATION"),
STANDARD_RANDOM_ACCESS_SERVICE_REQUEST(Vendor.STANDARD, 31, "RANDOM ACCESS SERVICE REQUEST"),
@@ -102,13 +102,18 @@ public enum Opcode
HYTERA_68_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA 68 XPT PREAMBLE"),
HYTERA_68_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA 68 CSBKO 62"),
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
private Vendor mVendor;
private int mValue;
private String mLabel;
/**
* Constructor
* @param vendor for the opcode
* @param value for the opcode
* @param label to display for the opcode
*/
Opcode(Vendor vendor, int value, String label)
{
mVendor = vendor;
@@ -151,19 +156,86 @@ public enum Opcode
*/
public boolean isDataChannelGrantOpcode()
{
return DATA_CHANNEL_GRANT_OPCODES.contains(this);
return DATA_CHANNEL_GRANTS.contains(this);
}
/**
* Data Channel Grant opcodes
* Data channel grant opcodes
*/
public static final EnumSet<Opcode> DATA_CHANNEL_GRANT_OPCODES = EnumSet.of(
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM,
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM,
STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT,
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM,
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM);
public static final EnumSet<Opcode> DATA_CHANNEL_GRANTS = EnumSet.of(STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM,
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM, STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT,
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM, STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM);
/**
* Data opcodes
*/
public static final EnumSet<Opcode> DATA_OPCODES = EnumSet.of(STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER,
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER, STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER,
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER, STANDARD_PREAMBLE);
/**
* Mobile request and response opcodes
*/
public static final EnumSet<Opcode> MOBILE_REQUEST_RESPONSE = EnumSet.of(STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST,
STANDARD_RANDOM_ACCESS_SERVICE_REQUEST);
/**
* Network request and response and announcement opcodes
*/
public static final EnumSet<Opcode> NETWORK_REQUEST_RESPONSE = EnumSet.of(STANDARD_FEATURE_NOT_SUPPORTED,
STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_RESPONSE, STANDARD_AHOY, STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC,
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_TSCC, STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD,
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD, STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE, STANDARD_CLEAR,
STANDARD_MOVE_TSCC, STANDARD_CHANNEL_TIMING,
STANDARD_ALOHA, STANDARD_ACTIVATION, STANDARD_ANNOUNCEMENT, STANDARD_MAINTENANCE, STANDARD_PROTECT);
/**
* Voice channel grant opcodes
*/
public static final EnumSet<Opcode> VOICE_CHANNEL_GRANTS = EnumSet.of(STANDARD_PRIVATE_VOICE_CHANNEL_GRANT,
STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT, STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT,
STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT);
/**
* Hytera opcodes
*/
public static final EnumSet<Opcode> HYTERA = EnumSet.of(HYTERA_08_ACKNOWLEDGE, HYTERA_08_ANNOUNCEMENT,
HYTERA_08_CSBKO_44, HYTERA_08_CSBKO_47, HYTERA_68_XPT_SITE_STATE, HYTERA_68_ALOHA,
HYTERA_68_ACKNOWLEDGE, HYTERA_68_ANNOUNCEMENT, HYTERA_68_XPT_PREAMBLE, HYTERA_68_CSBKO_62);
/**
* Motorola Capacity Max opcodes
*/
public static final EnumSet<Opcode> MOTOROLA_CAPACITY_MAX = EnumSet.of(MOTOROLA_CAPMAX_ALOHA);
/**
* Motorola Capacity Plus opcodes
*/
public static final EnumSet<Opcode> MOTOROLA_CAPACITY_PLUS = EnumSet.of(MOTOROLA_CAPPLUS_CALL_ALERT,
MOTOROLA_CAPPLUS_CALL_ALERT_ACK, MOTOROLA_CAPPLUS_DATA_WINDOW_ANNOUNCEMENT,
MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT, MOTOROLA_CAPPLUS_NEIGHBOR_REPORT, MOTOROLA_CAPPLUS_CSBKO_60,
MOTOROLA_CAPPLUS_PREAMBLE, MOTOROLA_CAPPLUS_SITE_STATUS);
/**
* Motorola Connect Plus opcodes
*/
public static final EnumSet<Opcode> MOTOROLA_CONNECT_PLUS = EnumSet.of(MOTOROLA_CONPLUS_NEIGHBOR_REPORT,
MOTOROLA_CONPLUS_VOICE_CHANNEL_USER, MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT, MOTOROLA_CONPLUS_CSBKO_10,
MOTOROLA_CONPLUS_TERMINATE_CHANNEL_GRANT, MOTOROLA_CONPLUS_CSBKO_16, MOTOROLA_CONPLUS_REGISTRATION_REQUEST,
MOTOROLA_CONPLUS_REGISTRATION_RESPONSE, MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION,
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT, MOTOROLA_CONPLUS_DATA_WINDOW_GRANT);
/**
* Indicates if the opcode is included in one of the enumset groupings above
* @return
*/
public boolean isGrouped()
{
return DATA_CHANNEL_GRANTS.contains(this) || DATA_OPCODES.contains(this) ||
MOBILE_REQUEST_RESPONSE.contains(this) || NETWORK_REQUEST_RESPONSE.contains(this) ||
VOICE_CHANNEL_GRANTS.contains(this) || HYTERA.contains(this) || MOTOROLA_CAPACITY_MAX.contains(this) ||
MOTOROLA_CAPACITY_PLUS.contains(this) || MOTOROLA_CONNECT_PLUS.contains(this);
}
/**
* Lookup map of vendors and opcode value to opcodes

View File

@@ -67,14 +67,14 @@ public enum LCOpcode
//CAP+ SLCO 15 = Rest Channel https://forums.radioreference.com/threads/understanding-capacity-plus-trunking.209318/page-7
SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION(Vendor.MOTOROLA_CAPACITY_PLUS, false,15, "REST CHANNEL NOTIFICATION"),
SHORT_STANDARD_XPT_CHANNEL(Vendor.STANDARD, false, 8, "HYTERA XPT CHANNEL"),
SHORT_STANDARD_XPT_CHANNEL(Vendor.STANDARD, false, 8, "STANDARD XPT CHANNEL"),
SHORT_HYTERA_XPT_CHANNEL(Vendor.HYTERA_68, false, 8, "HYTERA XPT CHANNEL"),
SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL(Vendor.STANDARD, false, 9, "TRAFFIC CHANNEL INFO"),
SHORT_CONNECT_PLUS_CONTROL_CHANNEL(Vendor.STANDARD, false, 10, "CONTROL CHANNEL INFO"),
SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "SHORT UNKNOWN");
SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "UNKNOWN");
private static final EnumSet<LCOpcode> TALKER_ALIAS_OPCODES = EnumSet.of(FULL_STANDARD_TALKER_ALIAS_HEADER,
FULL_STANDARD_TALKER_ALIAS_BLOCK_1, FULL_STANDARD_TALKER_ALIAS_BLOCK_2, FULL_STANDARD_TALKER_ALIAS_BLOCK_3);

View File

@@ -63,7 +63,6 @@ public class PacketSequenceMessageFactory
if(packetSequence.hasPacketSequenceHeader())
{
PacketSequenceHeader primaryHeader = packetSequence.getPacketSequenceHeader();
boolean confirmed = primaryHeader.isConfirmedData();
CorrectedBinaryMessage packet = getPacket(packetSequence, primaryHeader.isConfirmedData());
if(packet != null)

View File

@@ -0,0 +1,74 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
import java.util.Collection;
import java.util.function.Function;
/**
* Filter for opcode based control (CSBK) messages.
*/
public class ControlMessageFilter extends Filter<IMessage, Opcode>
{
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructs an instance
* @param name of this control message filter
* @param opcodes to use with this filter
*/
public ControlMessageFilter(String name, Collection<Opcode> opcodes)
{
super(name);
for(Opcode opcode: opcodes)
{
add(new FilterElement<>(opcode));
}
}
@Override
public Function<IMessage, Opcode> getKeyExtractor()
{
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,Opcode>
{
@Override
public Opcode apply(IMessage message)
{
if(message instanceof CSBKMessage csbk)
{
return csbk.getOpcode();
}
return null;
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
import java.util.ArrayList;
import java.util.List;
/**
* Filter set for DMR control (CSBK) messages using Opcodes for filter elements.
*/
public class ControlMessageFilterSet extends FilterSet<IMessage>
{
/**
* Constructor
*/
public ControlMessageFilterSet()
{
super("Control (CSBK) Messages");
addFilter(new ControlMessageFilter("Data Channel Grants", Opcode.DATA_CHANNEL_GRANTS));
addFilter(new ControlMessageFilter("Data-Related", Opcode.DATA_OPCODES));
addFilter(new ControlMessageFilter("Mobile Request/Response", Opcode.MOBILE_REQUEST_RESPONSE));
addFilter(new ControlMessageFilter("Network Request/Response", Opcode.NETWORK_REQUEST_RESPONSE));
addFilter(new ControlMessageFilter("Voice Channel Grants", Opcode.VOICE_CHANNEL_GRANTS));
addFilter(new ControlMessageFilter("Hytera", Opcode.HYTERA));
addFilter(new ControlMessageFilter("Motorola Capacity Max", Opcode.MOTOROLA_CAPACITY_MAX));
addFilter(new ControlMessageFilter("Motorola Capacity Plus", Opcode.MOTOROLA_CAPACITY_PLUS));
addFilter(new ControlMessageFilter("Motorola Connect Plus", Opcode.MOTOROLA_CONNECT_PLUS));
//Add all remaining opcodes that are not grouped into one of the above enumsets.
List<Opcode> otherOpcodes = new ArrayList<>();
for(Opcode opcode: Opcode.values())
{
if(!opcode.isGrouped())
{
otherOpcodes.add(opcode);
}
}
addFilter(new ControlMessageFilter("Other/Unknown", otherOpcodes));
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof CSBKMessage && super.canProcess(message);
}
}

View File

@@ -0,0 +1,80 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.function.Function;
/**
* Filter for data messages, excluding CSBK messages.
*/
public class DataMessageFilter extends Filter<IMessage, DataType>
{
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructs an instance
*
* @param name of this filter
*/
public DataMessageFilter()
{
super("Data Messages");
for(DataType dataType: DataType.values())
{
add(new FilterElement<>(dataType));
}
}
@Override
public Function<IMessage, DataType> getKeyExtractor()
{
return mKeyExtractor;
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof DataMessage && !(message instanceof CSBKMessage) && super.canProcess(message);
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,DataType>
{
@Override
public DataType apply(IMessage message)
{
if(message instanceof DataMessage dataMessage)
{
return dataMessage.getSlotType().getDataType();
}
return null;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,31 +17,29 @@
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter.lastheard;
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.module.ModuleEventBusMessage;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.filter.SyncLossMessageFilter;
import io.github.dsheirer.message.IMessage;
/**
* Response to decode event history request from a processing chain for the specified channel.
* Filter set for DMR messages
*/
public class LastHeardHistoryResponse extends ModuleEventBusMessage
public class DmrMessageFilterSet extends FilterSet<IMessage>
{
private LastHeardHistory mLastHeardHistory;
/**
* Constructs an instance
* @param history that has a decode event history
* Constructor
*/
public LastHeardHistoryResponse(LastHeardHistory history)
public DmrMessageFilterSet()
{
mLastHeardHistory = history;
}
/**
* Decode event history module
*/
public LastHeardHistory getDecodeEventHistory()
{
return mLastHeardHistory;
super("DMR Messages");
addFilter(new ControlMessageFilterSet());
addFilter(new VoiceMessageFilter());
addFilter(new DataMessageFilter());
addFilter(new DmrPacketSequenceFilter());
addFilter(new LinkControlMessageFilterSet());
addFilter(new SyncLossMessageFilter());
addFilter(new DmrOtherMessageFilter());
}
}

View File

@@ -0,0 +1,73 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import java.util.function.Function;
/**
* Filter for unknown messages, meaning messages not handled by any other filter.
*/
public class DmrOtherMessageFilter extends Filter<IMessage,String>
{
private static final String OTHER_KEY = "Other/Unknown Message";
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public DmrOtherMessageFilter()
{
super("Other DMR messages");
add(new FilterElement<>(OTHER_KEY));
}
@Override
public Function<IMessage, String> getKeyExtractor()
{
return mKeyExtractor;
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof DMRMessage && super.canProcess(message);
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
@Override
public String apply(IMessage message)
{
if(message instanceof DMRMessage)
{
return OTHER_KEY;
}
return null;
}
}
}

View File

@@ -0,0 +1,106 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
import java.util.function.Function;
/**
* Filter for DMR packet sequence messages
*/
public class DmrPacketSequenceFilter extends Filter<IMessage,String>
{
private static final String KEY_ARS = "Automatic Registration Service (ARS)";
private static final String KEY_HYTERA_SMS = "Hytera Short Message Service (SMS)";
private static final String KEY_HYTERA_UNKNOWN = "Hytera Unknown";
private static final String KEY_LRRP = "Location Request/Response Protocol (LRRP)";
private static final String KEY_UDT_SMS = "Unified Data Transport - Short Message Service";
private static final String KEY_XCMP = "Extensible Command Message Protocol (XCMP)";
private static final String KEY_UNKNOWN = "Other/Unknown";
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructs an instance
*/
public DmrPacketSequenceFilter()
{
super("Packet Sequence Messages");
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof DMRPacketMessage && super.canProcess(message);
}
@Override
public Function<IMessage, String> getKeyExtractor()
{
return mKeyExtractor;
}
private class KeyExtractor implements Function<IMessage,String>
{
@Override
public String apply(IMessage message)
{
if(message instanceof UDTShortMessageService)
{
return KEY_UDT_SMS;
}
else if(message instanceof DMRPacketMessage packetMessage)
{
if(packetMessage.getPacket() instanceof ARSPacket)
{
return KEY_ARS;
}
else if(packetMessage.getPacket() instanceof LRRPPacket)
{
return KEY_LRRP;
}
else if(packetMessage.getPacket() instanceof XCMPPacket)
{
return KEY_XCMP;
}
else if(packetMessage.getPacket() instanceof HyteraSmsPacket)
{
return KEY_HYTERA_SMS;
}
else if(packetMessage.getPacket() instanceof HyteraUnknownPacket)
{
return KEY_HYTERA_UNKNOWN;
}
//TODO: finish this
}
return KEY_UNKNOWN;
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
import java.util.Collection;
import java.util.function.Function;
/**
* Message filter for link control opcodes.
*
* Note: this does not include link control messages carried by data messages with embedded link control.
*/
public class LinkControlMessageFilter extends Filter<IMessage, LCOpcode>
{
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructs an instance
*
* @param name of this filter
*/
public LinkControlMessageFilter(String name, Collection<LCOpcode> opcodes)
{
super(name);
for(LCOpcode opcode: opcodes)
{
add(new FilterElement<>(opcode));
}
}
@Override
public Function<IMessage, LCOpcode> getKeyExtractor()
{
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,LCOpcode>
{
@Override
public LCOpcode apply(IMessage message)
{
if(message instanceof LCMessage lcMessage)
{
return lcMessage.getOpcode();
}
return null;
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
import java.util.ArrayList;
import java.util.List;
/**
* Filter set for short & voice-fragment link control messages
*
* Note: this filter set does NOT work for link control messages in Data Message with Link Control.
*/
public class LinkControlMessageFilterSet extends FilterSet<IMessage>
{
/**
* Constructor
*/
public LinkControlMessageFilterSet()
{
super("Short/Voice-Fragment Link Control Messages");
List<LCOpcode> fullLinkControl = new ArrayList<>();
List<LCOpcode> shortLinkControl = new ArrayList<>();
for(LCOpcode opcode: LCOpcode.values())
{
if(opcode.isFull())
{
fullLinkControl.add(opcode);
}
else
{
shortLinkControl.add(opcode);
}
}
addFilter(new LinkControlMessageFilter("Full Link Control", fullLinkControl));
addFilter(new LinkControlMessageFilter("Short Link Control", shortLinkControl));
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof LCMessage && super.canProcess(message);
}
}

View File

@@ -0,0 +1,86 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import java.util.function.Function;
/**
* Filter for voice messages
*/
public class VoiceMessageFilter extends Filter<IMessage, DMRSyncPattern>
{
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructs an instance
*/
public VoiceMessageFilter()
{
super("Voice Messages");
add(new FilterElement<>(DMRSyncPattern.BASE_STATION_VOICE));
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_B));
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_C));
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_D));
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_E));
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_F));
add(new FilterElement<>(DMRSyncPattern.MOBILE_STATION_VOICE));
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_B));
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_C));
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_D));
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_E));
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_F));
add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_1));
add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_2));
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof VoiceMessage && super.canProcess(message);
}
@Override
public Function<IMessage,DMRSyncPattern> getKeyExtractor()
{
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,DMRSyncPattern>
{
@Override
public DMRSyncPattern apply(IMessage message)
{
if(message instanceof VoiceMessage voice)
{
return voice.getSyncPattern();
}
return null;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,18 +27,18 @@ public enum DataType
VOICE_HEADER(1, 288, "VOICE HEADER"),
TLC(2, 288, "TERMINATOR"),
CSBK(3, 288, "CSBK"),
MBC_HEADER(4, 288, "MBC HEADER"),
MBC_BLOCK(5, 276, "MBC"),
MBC_HEADER(4, 288, "MULTI-BLOCK CONTROL HEADER"),
MBC_BLOCK(5, 276, "MULTI-BLOCK CONTROL BLOCK"),
DATA_HEADER(6, 288, "DATA HEADER"),
RATE_1_OF_2_DATA(7, 276, "RATE 1/2 PACKET"),
RATE_3_OF_4_DATA(8, 276, "RATE 3/4 PACKET"),
SLOT_IDLE(9, 276, "IDLE"),
RATE_1_DATA(10, 276, "RATE 1/1 PACKET"),
USB_DATA(11, 276, "UNIFIED SINGLE BLOCK DATA"),
MBC_ENC_HEADER(12, 276, "MBC ENCRYPTED HEADER"),
MBC_ENC_HEADER(12, 276, "MULTI-BLOCK CONTROL ENCRYPTED HEADER"),
DATA_ENC_HEADER(13, 276, "DATA ENCRYPTED HEADER"),
CHANNEL_CONTROL_ENC_HEADER(14, 276, "CONTROL CHANNEL ENCRYPTED HEADER"),
RESERVED_15(15, -1, "RESERVED"),
RESERVED_15(15, -1, "RESERVED 15"),
UNKNOWN(-1, -1, "UNKNOWN");
private int mValue;

View File

@@ -0,0 +1,125 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event;
import java.awt.EventQueue;
import java.util.LinkedList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
/**
* AbstractTableModel implementation supporting clearable method options.
*/
public abstract class ClearableHistoryModel<T> extends AbstractTableModel
{
public static final int DEFAULT_HISTORY_SIZE = 200;
private LinkedList<T> mItems = new LinkedList<>();
private int mHistorySize = DEFAULT_HISTORY_SIZE;
/**
* Access an item/row by the model index value.
* @param index to retrieve
* @return item or null
*/
public T getItem(int index)
{
if(index < mItems.size())
{
return mItems.get(index);
}
return null;
}
/**
* Adds the item to the top of the item list and removes any tail items while the item list size exceeds the
* maximum history size for this model.
* @param item to add
*/
public void add(T item)
{
mItems.addFirst(item);
ClearableHistoryModel.this.fireTableRowsInserted(0, 0);
while(mItems.size() > mHistorySize)
{
mItems.removeLast();
super.fireTableRowsDeleted(mItems.size() - 1, mItems.size() - 1);
}
}
/**
* Clears all messages from history
*/
public void clear()
{
EventQueue.invokeLater(() -> {
mItems.clear();
fireTableDataChanged();
});
}
/**
* Clears the current messages and loads the messages argument
*/
public void clearAndSet(List<T> items)
{
EventQueue.invokeLater(() -> {
mItems.clear();
fireTableDataChanged();
for(T item: items)
{
add(item);
}
});
}
/**
* Current history size
* @return history size
*/
public int getHistorySize()
{
return mHistorySize;
}
/**
* Resets the history size to the default value.
*/
public void resetHistorySize()
{
setHistorySize(DEFAULT_HISTORY_SIZE);
}
/**
* Sets the history size
* @param historySize
*/
public void setHistorySize(int historySize)
{
mHistorySize = historySize;
}
@Override
public int getRowCount()
{
return mItems.size();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,27 +32,33 @@ public class DecodeEvent implements IDecodeEvent
{
private long mTimeStart;
private long mTimeEnd;
private String mEventDescription;
private DecodeEventType mDecodeEventType;
private IdentifierCollection mIdentifierCollection;
private IChannelDescriptor mChannelDescriptor;
private String mDetails;
private Protocol mProtocol;
private Integer mTimeslot;
private int mTimeslot = -1;
public DecodeEvent(long start)
/**
* Constructs an instance
* @param decodeEventType is mandatory to support event filtering by event type.
* @param start time of the event.
*/
public DecodeEvent(DecodeEventType decodeEventType, long start)
{
mDecodeEventType = decodeEventType;
mTimeStart = start;
}
/**
* Creates a new decode event builder with the specified start timestamp.
* @param decodeEventType for the event
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
{
return new DecodeEventBuilder(timeStart);
return new DecodeEventBuilder(decodeEventType, timeStart);
}
/**
@@ -115,23 +121,6 @@ public class DecodeEvent implements IDecodeEvent
mTimeEnd = mTimeStart + duration;
}
/**
* Event description
*/
@Override
public String getEventDescription()
{
return mEventDescription;
}
/**
* Sets the event description text
*/
public void setEventDescription(String description)
{
mEventDescription = description;
}
/**
* {@link DecodeEventType}
*/
@@ -140,13 +129,6 @@ public class DecodeEvent implements IDecodeEvent
return mDecodeEventType;
}
/**
* Sets the {@link DecodeEventType}
*/
public void setEventType(DecodeEventType eventType) {
this.mDecodeEventType = eventType;
}
/**
* Identifier collection for this event.
*/
@@ -222,24 +204,24 @@ public class DecodeEvent implements IDecodeEvent
* @return timeslot or default value of 0
*/
@Override
public Integer getTimeslot()
public int getTimeslot()
{
return mTimeslot;
}
/**
* Indicates if this event specifies a timeslot
* Indicates if this event specifies a timeslot that is not negative
*/
public boolean hasTimeslot()
{
return mTimeslot != null;
return mTimeslot >= 0;
}
/**
* Sets the timeslot for this event
* @param timeslot of the event
*/
public void setTimeslot(Integer timeslot)
public void setTimeslot(int timeslot)
{
mTimeslot = timeslot;
}
@@ -249,7 +231,7 @@ public class DecodeEvent implements IDecodeEvent
{
StringBuilder sb = new StringBuilder();
sb.append(mProtocol);
sb.append(" DECODE EVENT: ").append(getEventDescription());
sb.append(" DECODE EVENT: ").append(getEventType());
sb.append(" DETAILS:").append(getDetails());
if(mIdentifierCollection != null)
{
@@ -267,19 +249,19 @@ public class DecodeEvent implements IDecodeEvent
{
protected long mTimeStart;
protected long mDuration;
protected String mEventDescription;
protected DecodeEventType mDecodeEventType;
protected IdentifierCollection mIdentifierCollection;
protected IChannelDescriptor mChannelDescriptor;
protected String mDetails;
protected Protocol mProtocol = Protocol.UNKNOWN;
protected Integer mTimeslot;
protected int mTimeslot = -1;
/**
* Constructs a builder instance with the specified start time in milliseconds
*/
public DecodeEventBuilder(long timeStart)
public DecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
{
mDecodeEventType = decodeEventType;
mTimeStart = timeStart;
}
@@ -313,25 +295,6 @@ 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;
}
/**
* Sets the event description text
* @param description of the event
*/
public DecodeEventBuilder eventDescription(String description)
{
mEventDescription = description;
return this;
}
/**
* Sets the identifier collection.
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
@@ -363,7 +326,12 @@ public class DecodeEvent implements IDecodeEvent
return this;
}
public DecodeEventBuilder timeslot(Integer timeslot)
/**
* Sets the timeslot for this event
* @param timeslot
* @return
*/
public DecodeEventBuilder timeslot(int timeslot)
{
mTimeslot = timeslot;
return this;
@@ -374,12 +342,10 @@ public class DecodeEvent implements IDecodeEvent
*/
public DecodeEvent build()
{
DecodeEvent decodeEvent = new DecodeEvent(mTimeStart);
DecodeEvent decodeEvent = new DecodeEvent(mDecodeEventType, mTimeStart);
decodeEvent.setChannelDescriptor(mChannelDescriptor);
decodeEvent.setDetails(mDetails);
decodeEvent.setDuration(mDuration);
decodeEvent.setEventType(mDecodeEventType);
decodeEvent.setEventDescription(mEventDescription);
decodeEvent.setIdentifierCollection(mIdentifierCollection);
decodeEvent.setProtocol(mProtocol);
decodeEvent.setTimeslot(mTimeslot);

View File

@@ -21,25 +21,20 @@ package io.github.dsheirer.module.decode.event;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.module.decode.event.filter.DecodeEventFilterSet;
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.awt.EventQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
public class DecodeEventModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
/**
* Decode event table model
*/
public class DecodeEventModel extends ClearableHistoryModel<IDecodeEvent> implements Listener<IDecodeEvent>
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(DecodeEventModel.class);
public static final int COLUMN_TIME = 0;
public static final int COLUMN_DURATION = 1;
public static final int COLUMN_EVENT = 2;
@@ -50,12 +45,6 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
public static final int COLUMN_CHANNEL = 7;
public static final int COLUMN_FREQUENCY = 8;
public static final int COLUMN_DETAILS = 9;
protected int mMaxMessages = 200;
protected List<IDecodeEvent> mEvents = new ArrayList<>();
protected FilterSet<IDecodeEvent> mEventFilterSet = new DecodeEventFilterSet();
protected String[] mHeaders = new String[]{"Time", "Duration", "Event", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Details"};
public DecodeEventModel()
@@ -70,72 +59,10 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
@Subscribe
public void preferenceUpdated(PreferenceType preferenceType)
{
if(preferenceType == PreferenceType.DECODE_EVENT)
if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.TALKGROUP_FORMAT)
{
for(int row = 0; row < mEvents.size(); row++)
{
fireTableCellUpdated(row, COLUMN_TIME);
}
fireTableDataChanged();
}
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<IDecodeEvent> getEvents()
{
return new ArrayList<>(mEvents);
}
public void dispose()
{
MyEventBus.getGlobalEventBus().unregister(this);
Iterator<IDecodeEvent> 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<IDecodeEvent> events)
{
mEvents.clear();
mEvents.addAll(events);
fireTableDataChanged();
}
public void reset()
{
dispose();
fireTableDataChanged();
}
public int getMaxMessageCount()
{
return mMaxMessages;
}
public void setMaxMessageCount(int count)
{
mMaxMessages = count;
}
/**
@@ -146,49 +73,7 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
*/
public void receive(final IDecodeEvent event)
{
//Disabled until #1368 decode event is fully implemented
// if (!mEventFilterSet.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<IDecodeEvent> getFilterSet() {
return mEventFilterSet;
}
@Override
public void setFilterSet(FilterSet<IDecodeEvent> filterSet) {
this.mEventFilterSet = filterSet;
}
@Override
public int getRowCount()
{
return mEvents.size();
EventQueue.invokeLater(() -> add(event));
}
@Override
@@ -205,58 +90,55 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
synchronized(mEvents)
IDecodeEvent event = getItem(rowIndex);
if(event != null)
{
IDecodeEvent event = mEvents.get(rowIndex);
if(event != null)
switch(columnIndex)
{
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();
case COLUMN_TIME:
return event.getTimeStart();
case COLUMN_DURATION:
return event.getDuration();
case COLUMN_EVENT:
return event.getEventType().getLabel();
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(channelDescriptor != null)
{
if(event.hasTimeslot())
{
if(event.hasTimeslot())
{
return channelDescriptor.toString() + " TS:" + event.getTimeslot();
}
else
{
return channelDescriptor.toString();
}
return channelDescriptor + " TS:" + event.getTimeslot();
}
else
{
if(event.hasTimeslot())
{
return "TS:" + event.getTimeslot();
}
else
{
return null;
}
return channelDescriptor.toString();
}
case COLUMN_FREQUENCY:
return event.getChannelDescriptor();
case COLUMN_DETAILS:
return event.getDetails();
}
}
else
{
if(event.hasTimeslot())
{
return "TS:" + event.getTimeslot();
}
else
{
return null;
}
}
case COLUMN_FREQUENCY:
return event.getChannelDescriptor();
case COLUMN_DETAILS:
return event.getDetails();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,7 +25,6 @@ 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;
@@ -33,10 +32,7 @@ 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.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.module.decode.event.filter.DecodeEventFilterSet;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
@@ -58,7 +54,10 @@ import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowFilter;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain>
{
@@ -75,7 +74,10 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
private AliasModel mAliasModel;
private UserPreferences mUserPreferences;
private TimestampCellRenderer mTimestampCellRenderer;
private EventManagementPanel mEventManagementPanel;
private FilterSet<IDecodeEvent> mFilterSet = new DecodeEventFilterSet();
private TableRowSorter<TableModel> mTableRowSorter;
private HistoryManagementPanel<IDecodeEvent> mHistoryManagementPanel;
/**
* View for call event table
@@ -91,17 +93,20 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
mUserPreferences = userPreferences;
mTimestampCellRenderer = new TimestampCellRenderer();
mTable = new JTable(mEventModel);
mTable.setAutoCreateRowSorter(true);
mTableRowSorter = new TableRowSorter<>(mEventModel);
mTableRowSorter.setRowFilter(new EventRowFilter());
mTable.setRowSorter(mTableRowSorter);
mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
mEventManagementPanel = new EventManagementPanel();
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
updateCellRenderers();
//Disabled until #1368 decode event is fully implemented
// add(mEventManagementPanel, "span,growx");
mHistoryManagementPanel = new HistoryManagementPanel<>(mEventModel, "Event Filter Editor");
mHistoryManagementPanel.updateFilterSet(mFilterSet);
add(mHistoryManagementPanel, "span,growx");
mEmptyScroller = new JScrollPane(mTable);
add(mEmptyScroller);
//Register filter change listener to refresh the table any time the event filters are changed.
mFilterSet.register(() -> mEventModel.fireTableDataChanged());
}
public void dispose()
@@ -124,22 +129,14 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
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());
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
@@ -156,76 +153,17 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
mCurrentEventHistory = processingChain.getDecodeEventHistory();
mEventModel.clearAndSet(mCurrentEventHistory.getItems());
processingChain.getDecodeEventHistory().addListener(mEventModel);
mEventManagementPanel.enableButtons();
mHistoryManagementPanel.setEnabled(true);
}
else
{
mCurrentEventHistory = null;
mEventModel.clearAndSet(Collections.emptyList());
mEventManagementPanel.disableButtons();
mHistoryManagementPanel.setEnabled(false);
}
});
}
public class EventManagementPanel extends JPanel
{
private static final long serialVersionUID = 1L;
private EventFilterButton<IDecodeEvent> mFilterButton;
private EventClearButton mEventClearButton;
public EventManagementPanel()
{
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<IDecodeEvent> filterProvider = (EventFilterProvider<IDecodeEvent>) mTable.getModel();
FilterSet<IDecodeEvent> filterSet = new FilterSet<>(new AllPassFilter<>());
if (filterProvider != null) {
filterSet = filterProvider.getFilterSet();
}
mFilterButton = new EventFilterButton<>("Message Filter Editor", filterSet);
}
private void initializeClearButton() {
mEventClearButton = new EventClearButton(
((DecodeEventModel) mTable.getModel()).getMaxMessageCount()
);
mEventClearButton.setEventClearHandler(new EventClearHandler() {
@Override
public void onHistoryLimitChanged(int newHistoryLimit) {
((DecodeEventModel) mTable.getModel()).setMaxMessageCount(newHistoryLimit);
}
@Override
public void onClearHistoryClicked() {
((DecodeEventModel) mTable.getModel()).clear();
}
});
}
}
/**
* Custom cell renderer for displaying identifiers from an identifier collection
*/
@@ -479,4 +417,26 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
setHorizontalAlignment(JLabel.CENTER);
}
}
/**
* Row filter for decode events
*/
public class EventRowFilter extends RowFilter<TableModel, Integer>
{
@Override
public boolean include(Entry<? extends TableModel, ? extends Integer> entry)
{
if(entry.getModel() instanceof DecodeEventModel model)
{
IDecodeEvent event = model.getItem(entry.getIdentifier());
if(event != null)
{
return mFilterSet.canProcess(event) && mFilterSet.passes(event);
}
}
return false;
}
}
}

View File

@@ -14,11 +14,17 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event;
import java.util.Arrays;
import java.util.EnumSet;
/**
* Enumeration of event types for decoded events.
*/
public enum DecodeEventType
{
AFFILIATE("Affiliate"),
@@ -43,6 +49,7 @@ public enum DecodeEventType
CALL_UNIT_TO_UNIT_ENCRYPTED("Encrypted Unit To Unit Call"),
CALL_NO_TUNER("Call - No Tuner"),
CALL_TIMEOUT("Call Timeout"),
CELLOCATOR("Cellocator"),
COMMAND("Command"),
DATA_CALL("Data Call"),
DATA_CALL_ENCRYPTED("Encrypted Data Call"),
@@ -55,6 +62,7 @@ public enum DecodeEventType
ID_ANI("ANI"),
ID_UNIQUE("Unique ID"),
IP_PACKET("IP Packet"),
LRRP("Motorola LRRP"),
NOTIFICATION("Notification"),
PAGE("Page"),
QUERY("Query"),
@@ -74,16 +82,82 @@ public enum DecodeEventType
private String mLabel;
/**
* Encrypted voice call event types for filtering
*/
public static final EnumSet<DecodeEventType> VOICE_CALLS_ENCRYPTED = EnumSet.of(DecodeEventType.CALL_ENCRYPTED,
DecodeEventType.CALL_GROUP_ENCRYPTED, DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED,
DecodeEventType.CALL_INTERCONNECT_ENCRYPTED, DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED);
/**
* Voice call event types for filtering
*/
public static final EnumSet<DecodeEventType> VOICE_CALLS = EnumSet.of(DecodeEventType.CALL_GROUP,
DecodeEventType.CALL_PATCH_GROUP, DecodeEventType.CALL_ALERT, DecodeEventType.CALL_DETECT,
DecodeEventType.CALL_DO_NOT_MONITOR, DecodeEventType.CALL_END, DecodeEventType.CALL_INTERCONNECT,
DecodeEventType.CALL_UNIQUE_ID, DecodeEventType.CALL_UNIT_TO_UNIT, DecodeEventType.CALL_NO_TUNER,
DecodeEventType.CALL_TIMEOUT);
/**
* Command event types for filtering
*/
public static final EnumSet<DecodeEventType> COMMANDS = EnumSet.of(DecodeEventType.ANNOUNCEMENT,
DecodeEventType.STATION_ID, DecodeEventType.ACKNOWLEDGE, DecodeEventType.PAGE, DecodeEventType.QUERY,
DecodeEventType.RADIO_CHECK, DecodeEventType.STATUS, DecodeEventType.COMMAND, DecodeEventType.EMERGENCY,
DecodeEventType.NOTIFICATION, DecodeEventType.FUNCTION);
/**
* Data call event types for filtering
*/
public static final EnumSet<DecodeEventType> DATA_CALLS = EnumSet.of(DecodeEventType.DATA_CALL,
DecodeEventType.DATA_CALL_ENCRYPTED, DecodeEventType.DATA_PACKET, DecodeEventType.GPS,
DecodeEventType.IP_PACKET, DecodeEventType.UDP_PACKET, DecodeEventType.SDM, DecodeEventType.ID_ANI,
DecodeEventType.ID_UNIQUE);
/**
* Registration event types for filtering
*/
public static final EnumSet<DecodeEventType> REGISTRATION = EnumSet.of(DecodeEventType.AFFILIATE,
DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE, DecodeEventType.REGISTER, DecodeEventType.REGISTER_ESN,
DecodeEventType.DEREGISTER, DecodeEventType.REQUEST, DecodeEventType.RESPONSE, DecodeEventType.RESPONSE_PACKET);
/**
* All other event types of this enumeration that are not included in the groupings above.
*/
public static final EnumSet<DecodeEventType> OTHERS = EnumSet.copyOf(Arrays.stream(DecodeEventType.values())
.filter(decodeEventType -> !decodeEventType.isGrouped()).toList());
/**
* Constructor
* @param label for the element
*/
DecodeEventType(String label)
{
mLabel = label;
}
/**
* Indicates if the enumeration element is contained in one of the enumset groupings above.
* @return true if the element is grouped.
*/
public boolean isGrouped()
{
return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || COMMANDS.contains(this) ||
DATA_CALLS.contains(this) || REGISTRATION.contains(this);
}
/**
* Label or pretty value for the element
* @return label
*/
public String getLabel()
{
return mLabel;
}
/**
* Uses label as the default string value.
*/
public String toString()
{
return mLabel;

View File

@@ -0,0 +1,212 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event;
import io.github.dsheirer.filter.FilterEditor;
import io.github.dsheirer.filter.FilterSet;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import net.miginfocom.swing.MigLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
/**
* History management panel with controls for managing item histories.
*/
public class HistoryManagementPanel<T> extends JPanel
{
private ClearableHistoryModel mModel;
private FilterSet<T> mFilterSet;
private FilterEditor<T> mFilterEditor;
private JButton mClearButton;
private JButton mFilterButton;
private JSlider mHistorySlider;
private JLabel mHistoryTitleLabel;
private JLabel mHistoryValueLabel;
private String mFilterEditorTitle;
/**
* Constructs an instance
* @param model to manage
*/
public HistoryManagementPanel(ClearableHistoryModel model, String filterEditorTitle)
{
mModel = model;
mFilterEditorTitle = filterEditorTitle;
setLayout(new MigLayout("insets 6 1 5 5", "[]5[]10[]5[]5[][grow]", ""));
add(getFilterButton());
add(getClearButton());
add(getHistoryTitleLabel());
add(getHistorySlider());
add(getHistoryValueLabel());
setEnabled(false);
}
/**
* Updates the filter set
* @param filterSet to use
*/
public void updateFilterSet(FilterSet<T> filterSet)
{
mFilterSet = filterSet;
if(mFilterEditor != null)
{
getFilterEditor().updateFilterSet(filterSet);
}
}
/**
* Overrides the panel method to also set the enabled state for the child controls.
* @param enabled true if this component should be enabled, false otherwise
*/
@Override
public void setEnabled(boolean enabled)
{
super.setEnabled(enabled);
getClearButton().setEnabled(enabled);
getFilterButton().setEnabled(enabled);
getHistoryValueLabel().setEnabled(enabled);
getHistoryTitleLabel().setEnabled(enabled);
getHistorySlider().setEnabled(enabled);
}
/**
* Filter editor
* @return editor lazily constructed
*/
private FilterEditor<T> getFilterEditor()
{
if(mFilterEditor == null)
{
mFilterEditor = new FilterEditor<>(mFilterEditorTitle, getFilterButton(), mFilterSet);
}
return mFilterEditor;
}
/**
* Filter button for accessing the filter editor
* @return filter button
*/
private JButton getFilterButton()
{
if(mFilterButton == null)
{
mFilterButton = new JButton("Filters");
mFilterButton.setToolTipText("Edit filters");
mFilterButton.addActionListener(arg0 -> EventQueue.invokeLater(() -> getFilterEditor().setVisible(true)));
}
return mFilterButton;
}
/**
* Clear button
* @return clear button
*/
private JButton getClearButton()
{
if(mClearButton == null)
{
mClearButton = new JButton("Clear");
mClearButton.addActionListener(e -> mModel.clear());
mClearButton.setToolTipText("Clears the history");
}
return mClearButton;
}
/**
* History value label.
* @return label
*/
private JLabel getHistoryValueLabel()
{
if(mHistoryValueLabel == null)
{
mHistoryValueLabel = new JLabel(String.valueOf(ClearableHistoryModel.DEFAULT_HISTORY_SIZE));
}
return mHistoryValueLabel;
}
/**
* History title label
* @return label
*/
private JLabel getHistoryTitleLabel()
{
if(mHistoryTitleLabel == null)
{
mHistoryTitleLabel = new JLabel("History:");
}
return mHistoryTitleLabel;
}
/**
* History value slider control
* @return slider
*/
private JSlider getHistorySlider()
{
if(mHistorySlider == null)
{
mHistorySlider = new JSlider();
mHistorySlider.setToolTipText("Adjust history size. Double-click to reset to default 200");
mHistorySlider.setMinimum(0);
mHistorySlider.setMaximum(2000);
mHistorySlider.setMinorTickSpacing(25);
mHistorySlider.setMajorTickSpacing(500);
mHistorySlider.setPaintTicks(false);
mHistorySlider.setPaintLabels(false);
mHistorySlider.addMouseListener(new MouseListener()
{
@Override
public void mouseClicked(MouseEvent arg0)
{
if(SwingUtilities.isLeftMouseButton(arg0) && arg0.getClickCount() == 2)
{
mHistorySlider.setValue(ClearableHistoryModel.DEFAULT_HISTORY_SIZE);
}
}
public void mouseEntered(MouseEvent arg0) {}
public void mouseExited(MouseEvent arg0) {}
public void mousePressed(MouseEvent arg0) {}
public void mouseReleased(MouseEvent arg0) {}
});
mHistorySlider.setValue(mModel.getHistorySize());
mHistorySlider.addChangeListener(e -> {
mModel.setHistorySize(mHistorySlider.getValue());
getHistoryValueLabel().setText(String.valueOf(mHistorySlider.getValue()));
});
}
return mHistorySlider;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -48,11 +48,6 @@ public interface IDecodeEvent
*/
long getDuration();
/**
* A description of the event or event type
*/
String getEventDescription();
/**
* Collection of identifiers associated with the event. This collection should contain a
* Role.FROM and a Role.TO identifier, a Decoder Type identifier, and (optionally) an Alias List
@@ -86,7 +81,7 @@ public interface IDecodeEvent
* Timeslot for the event.
* @return timeslot or default of 0
*/
Integer getTimeslot();
int getTimeslot();
/**
* Indicates if the event has a timeslot specified

View File

@@ -18,19 +18,16 @@
*/
package io.github.dsheirer.module.decode.event;
import io.github.dsheirer.filter.AllPassFilter;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.StuffBitsMessage;
import io.github.dsheirer.sample.Listener;
import java.awt.EventQueue;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;
import javax.swing.table.AbstractTableModel;
public class MessageActivityModel extends AbstractTableModel implements Listener<IMessage>
/**
* Table Model for decoded IMessages.
*/
public class MessageActivityModel extends ClearableHistoryModel<MessageItem> implements Listener<IMessage>
{
private static final long serialVersionUID = 1L;
private static final int TIME = 0;
@@ -38,95 +35,20 @@ public class MessageActivityModel extends AbstractTableModel implements Listener
private static final int TIMESLOT = 2;
private static final int MESSAGE = 3;
protected int mMaxMessages = 200;
protected LinkedList<MessageItem> mMessageItems = new LinkedList<>();
protected int[] mColumnWidths = {20, 20, 500};
protected String[] mHeaders = new String[]{"Time", "Protocol", "Timeslot", "Message"};
private String[] mHeaders = new String[]{"Time", "Protocol", "Timeslot", "Message"};
private SimpleDateFormat mSDFTime = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
private FilterSet<IMessage> mMessageFilterSet = new FilterSet<>(new AllPassFilter<>());
/**
* Constructor
*/
public MessageActivityModel()
{
}
/**
* Applies the filter set
* Implements the listener interface and wraps the IMessage in table-compatible message item wrapper.
* @param message to add to the model
*/
public void setFilters(FilterSet filterSet)
{
mMessageFilterSet = filterSet;
}
public void clearFilters()
{
mMessageFilterSet = new FilterSet<>(new AllPassFilter<>());
}
/**
* Clears all messages from history
*/
public void clear()
{
EventQueue.invokeLater(() -> {
mMessageItems.clear();
fireTableDataChanged();
});
}
/**
* Clears the current messages and loads the messages argument
*/
public void clearAndSet(List<IMessage> messages)
{
EventQueue.invokeLater(() -> {
mMessageItems.clear();
fireTableDataChanged();
for(IMessage message: messages)
{
receive(message);
}
});
}
public FilterSet<IMessage> getMessageFilterSet()
{
return mMessageFilterSet;
}
public void dispose()
{
mMessageItems.clear();
}
public int[] getColumnWidths()
{
return mColumnWidths;
}
public void setColumnWidths(int[] widths)
{
if(widths.length != 3)
{
throw new IllegalArgumentException("MessageActivityModel - column widths array should have 3 elements");
}
else
{
mColumnWidths = widths;
}
}
public int getMaxMessageCount()
{
return mMaxMessages;
}
public void setMaxMessageCount(int count)
{
mMaxMessages = count;
}
public void receive(final IMessage message)
{
//Don't process tail bits or stuff bits message fragments
@@ -135,39 +57,7 @@ public class MessageActivityModel extends AbstractTableModel implements Listener
return;
}
if(mMessageFilterSet.passes(message))
{
final MessageItem messageItem = new MessageItem(message);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mMessageItems.addFirst(messageItem);
MessageActivityModel.this.fireTableRowsInserted(0, 0);
prune();
}
});
}
}
private void prune()
{
while(mMessageItems.size() > mMaxMessages)
{
MessageItem removed = mMessageItems.removeLast();
removed.dispose();
super.fireTableRowsDeleted(mMessageItems.size() - 1, mMessageItems.size() - 1);
}
}
@Override
public int getRowCount()
{
return mMessageItems.size();
EventQueue.invokeLater(() -> add(new MessageItem(message)));
}
@Override
@@ -184,20 +74,20 @@ public class MessageActivityModel extends AbstractTableModel implements Listener
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
if(0 <= rowIndex && rowIndex < mMessageItems.size())
{
MessageItem messageItem = mMessageItems.get(rowIndex);
MessageItem item = getItem(rowIndex);
if(item != null)
{
switch(columnIndex)
{
case TIME:
return messageItem.getTimestamp(mSDFTime);
return item.getTimestamp(mSDFTime);
case PROTOCOL:
return messageItem.getProtocol();
return item.getProtocol();
case TIMESLOT:
return messageItem.getTimeslot();
return item.getTimeslot();
case MESSAGE:
return messageItem.getText();
return item.getText();
default:
break;
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,13 +14,10 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event;
import com.jidesoft.swing.JideButton;
import com.jidesoft.swing.JideSplitButton;
import io.github.dsheirer.filter.FilterEditorPanel;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.MessageHistory;
@@ -30,37 +26,35 @@ import io.github.dsheirer.module.decode.DecoderFactory;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.RowFilter;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
/**
* Panel to display decoded messages/activity.
*/
public class MessageActivityPanel extends JPanel implements Listener<ProcessingChain>
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(MessageActivityPanel.class);
private static final String TABLE_PREFERENCE_KEY = "message.activity.panel";
private static MessageActivityModel mMessageModel = new MessageActivityModel();
private static final Logger mLog = LoggerFactory.getLogger(MessageActivityPanel.class);
private final String TABLE_PREFERENCE_KEY = "message.activity.panel";
private MessageActivityModel mMessageModel = new MessageActivityModel();
private MessageHistory mCurrentMessageHistory;
private JTable mTable = new JTable(mMessageModel);
private TableRowSorter<TableModel> mTableRowSorter;
private JTableColumnWidthMonitor mTableColumnWidthMonitor;
private UserPreferences mUserPreferences;
private MessageManagementPanel mManagementPanel = new MessageManagementPanel();
private FilterSet<IMessage> mMessageFilterSet;
private HistoryManagementPanel<IMessage> mHistoryManagementPanel;
/**
* Constructs an instance
@@ -69,13 +63,14 @@ public class MessageActivityPanel extends JPanel implements Listener<ProcessingC
public MessageActivityPanel(UserPreferences userPreferences)
{
mUserPreferences = userPreferences;
setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill]", "[]0[grow,fill]"));
add(mManagementPanel, "span,growx");
add(new JScrollPane(mTable), "span,grow");
mTableRowSorter = new TableRowSorter<>(mMessageModel);
mTableRowSorter.setRowFilter(new MessageRowFilter());
mTable.setRowSorter(mTableRowSorter);
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill]", "[]0[grow,fill]"));
mHistoryManagementPanel = new HistoryManagementPanel<>(mMessageModel, "Message Filter Editor");
add(mHistoryManagementPanel, "span,growx");
add(new JScrollPane(mTable), "span,grow");
}
/**
@@ -89,182 +84,62 @@ public class MessageActivityPanel extends JPanel implements Listener<ProcessingC
mCurrentMessageHistory.removeListener(mMessageModel);
}
//Unregister from changes made to the filter set
if(mMessageFilterSet != null)
{
mMessageFilterSet.register(null);
}
if(processingChain != null)
{
mCurrentMessageHistory = processingChain.getMessageHistory();
mMessageModel.setFilters(DecoderFactory.getMessageFilters(processingChain.getModules()));
mMessageModel.clearAndSet(mCurrentMessageHistory.getItems());
mMessageFilterSet = DecoderFactory.getMessageFilters(processingChain.getModules());
//Register filter change listener to refresh the table any time the event filters are changed.
mMessageFilterSet.register(() -> mMessageModel.fireTableDataChanged());
if(mHistoryManagementPanel != null)
{
mHistoryManagementPanel.updateFilterSet(mMessageFilterSet);
}
List<MessageItem> currentHistory = new ArrayList<>();
for(IMessage message: mCurrentMessageHistory.getItems())
{
currentHistory.add(new MessageItem(message));
}
mMessageModel.clearAndSet(currentHistory);
mCurrentMessageHistory.addListener(mMessageModel);
mManagementPanel.enableButtons();
mHistoryManagementPanel.setEnabled(true);
}
else
{
mCurrentMessageHistory = null;
mMessageModel.clearFilters();
mMessageFilterSet = null;
mMessageModel.clear();
mManagementPanel.disableButtons();
mHistoryManagementPanel.setEnabled(false);
}
}
public class MessageManagementPanel extends JPanel
/**
* Row visibility filter for messages
*/
public class MessageRowFilter extends RowFilter<TableModel, Integer>
{
private static final long serialVersionUID = 1L;
private MessageHistoryButton mHistoryButton = new MessageHistoryButton();
private MessageFilterButton mFilterButton = new MessageFilterButton();
public MessageManagementPanel()
@Override
public boolean include(Entry<? extends TableModel, ? extends Integer> entry)
{
setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", ""));
disableButtons();
add(mFilterButton);
add(mHistoryButton);
}
public void enableButtons()
{
mHistoryButton.setEnabled(true);
mFilterButton.setEnabled(true);
}
public void disableButtons()
{
mHistoryButton.setEnabled(false);
mFilterButton.setEnabled(false);
}
}
public class MessageHistoryButton extends JideSplitButton
{
private static final long serialVersionUID = 1L;
public MessageHistoryButton()
{
super("Clear");
JPanel historyPanel = new JPanel();
historyPanel.add(new JLabel("Message History:"));
final JSlider slider = new JSlider();
slider.setMinimum(0);
slider.setMaximum(2000);
slider.setMajorTickSpacing(500);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
slider.addMouseListener(new MouseListener()
if(entry.getModel() instanceof MessageActivityModel model)
{
@Override
public void mouseClicked(MouseEvent arg0)
MessageItem item = model.getItem(entry.getIdentifier());
if(mMessageFilterSet != null && item != null && item.getMessage() != null)
{
if(SwingUtilities.isLeftMouseButton(arg0) && arg0.getClickCount() == 2)
{
slider.setValue(500); //default
}
IMessage message = item.getMessage();
return mMessageFilterSet.canProcess(message) && mMessageFilterSet.passes(message);
}
}
public void mouseEntered(MouseEvent arg0)
{
}
public void mouseExited(MouseEvent arg0)
{
}
public void mousePressed(MouseEvent arg0)
{
}
public void mouseReleased(MouseEvent arg0)
{
}
});
slider.setValue(((MessageActivityModel) mTable.getModel()).getMaxMessageCount());
slider.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent arg0)
{
((MessageActivityModel) mTable.getModel()).setMaxMessageCount(slider.getValue());
}
});
historyPanel.add(slider);
add(historyPanel);
/* Clear messages */
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
((MessageActivityModel) mTable.getModel()).clear();
}
});
}
}
public class MessageFilterButton extends JideButton
{
private static final long serialVersionUID = 1L;
public MessageFilterButton()
{
super("Filter");
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
final JFrame editor = new JFrame();
editor.setTitle("Message Filter Editor");
editor.setSize(600, 400);
editor.setLocationRelativeTo(MessageFilterButton.this);
editor.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
editor.setLayout(new MigLayout("", "[grow,fill]",
"[grow,fill][][]"));
@SuppressWarnings("unchecked")
FilterSet<IMessage> filter = (FilterSet<IMessage>) ((MessageActivityModel) mTable.getModel())
.getMessageFilterSet();
FilterEditorPanel<IMessage> panel = new FilterEditorPanel<>(filter);
JScrollPane scroller = new JScrollPane(panel);
scroller.setViewportView(panel);
editor.add(scroller, "wrap");
editor.add(new JLabel("Right-click to select/deselect all nodes"), "wrap");
JButton close = new JButton("Close");
close.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
editor.dispose();
}
});
editor.add(close);
EventQueue.invokeLater(new Runnable()
{
public void run()
{
editor.setVisible(true);
}
});
}
});
return false;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event;
@@ -31,19 +30,20 @@ public class PlottableDecodeEvent extends DecodeEvent
private double mHeading;
private double mSpeed;
public PlottableDecodeEvent(long start)
public PlottableDecodeEvent(DecodeEventType decodeEventType, long start)
{
super(start);
super(decodeEventType, start);
}
/**
* Creates a new decode event builder with the specified start timestamp.
* @param decodeEventType for the event
* @param timeStart for the event
* @return builder
*/
public static PlottableDecodeEventBuilder plottableBuilder(long timeStart)
public static PlottableDecodeEventBuilder plottableBuilder(DecodeEventType decodeEventType, long timeStart)
{
return new PlottableDecodeEventBuilder(timeStart);
return new PlottableDecodeEventBuilder(decodeEventType, timeStart);
}
/**
@@ -102,7 +102,6 @@ public class PlottableDecodeEvent extends DecodeEvent
{
private long mTimeStart;
private long mDuration;
private String mEventDescription;
private DecodeEventType mDecodeEventType;
private IdentifierCollection mIdentifierCollection;
private IChannelDescriptor mChannelDescriptor;
@@ -115,8 +114,9 @@ public class PlottableDecodeEvent extends DecodeEvent
/**
* Constructs a builder instance with the specified start time in milliseconds
*/
public PlottableDecodeEventBuilder(long timeStart)
public PlottableDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
{
mDecodeEventType = decodeEventType;
mTimeStart = timeStart;
}
@@ -150,16 +150,6 @@ public class PlottableDecodeEvent extends DecodeEvent
return this;
}
/**
* Sets the event description text
* @param description of the event
*/
public PlottableDecodeEventBuilder eventDescription(String description)
{
mEventDescription = description;
return this;
}
/**
* Sets the identifier collection.
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
@@ -191,15 +181,6 @@ 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
*/
@@ -232,12 +213,10 @@ public class PlottableDecodeEvent extends DecodeEvent
*/
public PlottableDecodeEvent build()
{
PlottableDecodeEvent decodeEvent = new PlottableDecodeEvent(mTimeStart);
PlottableDecodeEvent decodeEvent = new PlottableDecodeEvent(mDecodeEventType, mTimeStart);
decodeEvent.setChannelDescriptor(mChannelDescriptor);
decodeEvent.setDetails(mDetails);
decodeEvent.setDuration(mDuration);
decodeEvent.setEventType(mDecodeEventType);
decodeEvent.setEventDescription(mEventDescription);
decodeEvent.setIdentifierCollection(mIdentifierCollection);
decodeEvent.setProtocol(mProtocol);
decodeEvent.setLocation(mGeoPosition);
@@ -246,5 +225,4 @@ public class PlottableDecodeEvent extends DecodeEvent
return decodeEvent;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,19 +17,23 @@
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter.lastheard;
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.module.ModuleEventBusMessage;
import io.github.dsheirer.module.decode.event.DecodeEventType;
/**
* Request for decode event history from a processing chain for the specified channel.
* Event filter for decode event types that are not specified in other event filters.
*
* This is intended as a catch-all for any elements of the DecodeEventType enumeration that may get added in the
* future and are not deliberately added to the various event filter groupings listed in the enumeration.
*/
public class LastHeardHistoryRequest extends ModuleEventBusMessage
public class AllOtherEventFilter extends EventFilter
{
/**
* Constructs an instance
* Constructor
*/
public LastHeardHistoryRequest()
public AllOtherEventFilter()
{
super("Other", DecodeEventType.OTHERS);
}
}

View File

@@ -1,16 +1,40 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
public class DecodeEventFilterSet extends FilterSet<IDecodeEvent> {
public DecodeEventFilterSet() {
super ("All Messages");
/**
* Top-level decode event filter set.
*/
public class DecodeEventFilterSet extends FilterSet<IDecodeEvent>
{
public DecodeEventFilterSet()
{
super ("All Events");
addFilter(new DecodedCallEventFilter());
addFilter(new DecodedCallEncryptedEventFilter());
addFilter(new DecodedDataEventFilter());
addFilter(new DecodedCommandEventFilter());
addFilter(new DecodedRegistrationEventFilter());
addFilter(new AllOtherEventFilter());
}
}

View File

@@ -1,19 +1,36 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import java.util.Arrays;
/**
* Encrypted call events filter
*/
public class DecodedCallEncryptedEventFilter extends EventFilter
{
/**
* Constructs an instance
*/
public DecodedCallEncryptedEventFilter()
{
super("Voice Calls - Encrypted", Arrays.asList(
DecodeEventType.CALL_ENCRYPTED,
DecodeEventType.CALL_GROUP_ENCRYPTED,
DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED,
DecodeEventType.CALL_INTERCONNECT_ENCRYPTED,
DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED
));
super("Voice Calls - Encrypted", DecodeEventType.VOICE_CALLS_ENCRYPTED);
}
}

View File

@@ -1,25 +1,36 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import java.util.Arrays;
/**
* Call events filter
*/
public class DecodedCallEventFilter extends EventFilter
{
/**
* Constructs an instance
*/
public DecodedCallEventFilter()
{
super("Voice Calls", Arrays.asList(
DecodeEventType.CALL_GROUP,
DecodeEventType.CALL_PATCH_GROUP,
DecodeEventType.CALL_ALERT,
DecodeEventType.CALL_DETECT,
DecodeEventType.CALL_DO_NOT_MONITOR,
DecodeEventType.CALL_END,
DecodeEventType.CALL_INTERCONNECT,
DecodeEventType.CALL_UNIQUE_ID,
DecodeEventType.CALL_UNIT_TO_UNIT,
DecodeEventType.CALL_NO_TUNER,
DecodeEventType.CALL_TIMEOUT
));
super("Voice Calls", DecodeEventType.VOICE_CALLS);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,24 +20,17 @@
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import java.util.Arrays;
/**
* Event filter for network commands.
*/
public class DecodedCommandEventFilter extends EventFilter
{
/**
* Constructs an instance
*/
public DecodedCommandEventFilter()
{
super("Commands", Arrays.asList(
DecodeEventType.ANNOUNCEMENT,
DecodeEventType.STATION_ID,
DecodeEventType.ACKNOWLEDGE,
DecodeEventType.PAGE,
DecodeEventType.QUERY,
DecodeEventType.RADIO_CHECK,
DecodeEventType.STATUS,
DecodeEventType.COMMAND,
DecodeEventType.EMERGENCY,
DecodeEventType.NOTIFICATION,
DecodeEventType.FUNCTION
));
super("Commands", DecodeEventType.COMMANDS);
}
}

View File

@@ -1,23 +1,36 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import java.util.Arrays;
/**
* Event filter for data call event types
*/
public class DecodedDataEventFilter extends EventFilter
{
/**
* Constructs an instance
*/
public DecodedDataEventFilter()
{
super("Data Calls", Arrays.asList(
DecodeEventType.DATA_CALL,
DecodeEventType.DATA_CALL_ENCRYPTED,
DecodeEventType.DATA_PACKET,
DecodeEventType.GPS,
DecodeEventType.IP_PACKET,
DecodeEventType.UDP_PACKET,
DecodeEventType.SDM,
DecodeEventType.ID_ANI,
DecodeEventType.ID_UNIQUE
));
super("Data Calls", DecodeEventType.DATA_CALLS);
}
}

View File

@@ -1,22 +1,36 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import java.util.Arrays;
/**
* Event filter for registration related events
*/
public class DecodedRegistrationEventFilter extends EventFilter
{
/**
* Constructor
*/
public DecodedRegistrationEventFilter()
{
super("Registrations", Arrays.asList(
DecodeEventType.AFFILIATE,
DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE,
DecodeEventType.REGISTER,
DecodeEventType.REGISTER_ESN,
DecodeEventType.DEREGISTER,
DecodeEventType.REQUEST,
DecodeEventType.RESPONSE,
DecodeEventType.RESPONSE_PACKET
));
super("Registrations", DecodeEventType.REGISTRATION);
}
}

View File

@@ -1,15 +1,33 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
import com.jidesoft.swing.JideSplitButton;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class EventClearButton extends JideSplitButton
{
private static final long serialVersionUID = 1L;
@@ -21,37 +39,29 @@ public class EventClearButton extends JideSplitButton
JPanel historyPanel = new JPanel();
historyPanel.add(new JLabel("History Entries:"));
historyPanel.add(new JLabel("History Size:"));
final JSlider historySlider = initializeHistorySlider();
JLabel valueLabel = new JLabel(String.valueOf(maxHistoryCount));
historySlider.setValue(maxHistoryCount);
historySlider.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent arg0)
historySlider.addChangeListener(arg0 -> {
if (mEventClearHandler != null)
{
if (mEventClearHandler != null)
{
mEventClearHandler.onHistoryLimitChanged(historySlider.getValue());
}
mEventClearHandler.onHistoryLimitChanged(historySlider.getValue());
}
valueLabel.setText(String.valueOf(historySlider.getValue()));
});
historyPanel.add(historySlider);
historyPanel.add(valueLabel);
add(historyPanel);
/* This handles the click action on the main button. Clear messages */
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
addActionListener(e -> {
if (mEventClearHandler != null)
{
if (mEventClearHandler != null)
{
mEventClearHandler.onClearHistoryClicked();
}
mEventClearHandler.onClearHistoryClicked();
}
});
}

View File

@@ -1,6 +1,37 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
public interface EventClearHandler {
/**
* Handler interface for changes to the event history size or content.
*/
public interface EventClearHandler
{
/**
* Invoked when the retained history size limit is changed
* @param newHistoryLimit new size
*/
void onHistoryLimitChanged(int newHistoryLimit);
/**
* Invoked when the history is cleared
*/
void onClearHistoryClicked();
}

View File

@@ -1,56 +1,74 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.function.Function;
/**
* This is used as a base for {@link IDecodeEvent} types.
* This will take a list of {@link IDecodeEvent}s and generate filters for the list.
* This will default canProcess to TRUE. Override if other value or evaluation is needed.
*
*/
public class EventFilter extends Filter<IDecodeEvent>
public class EventFilter extends Filter<IDecodeEvent,DecodeEventType>
{
private Map<DecodeEventType, FilterElement<DecodeEventType>> mElements = new EnumMap<>(DecodeEventType.class);
private EventKeyExtractor mKeyExtractor = new EventKeyExtractor();
public EventFilter(String name, List<DecodeEventType> eventTypes)
/**
* Constructs an instance
* @param name of this filter
* @param decodeEventTypes to filter against
*/
public EventFilter(String name, Collection<DecodeEventType> decodeEventTypes)
{
super(name);
for (DecodeEventType eventType : eventTypes) {
mElements.put(eventType, new FilterElement<>(eventType));
}
}
@Override
public boolean passes(IDecodeEvent iDecodeEvent)
{
if (mEnabled && canProcess(iDecodeEvent))
for (DecodeEventType decodeEventType : decodeEventTypes)
{
if (mElements.containsKey(iDecodeEvent.getEventType()))
{
return mElements.get(iDecodeEvent.getEventType()).isEnabled();
}
add(new FilterElement<>(decodeEventType));
}
return false;
}
/**
* Key extractor function
* @return function.
*/
@Override
public boolean canProcess(IDecodeEvent iDecodeEvent)
public Function<IDecodeEvent, DecodeEventType> getKeyExtractor()
{
return true;
return mKeyExtractor;
}
@Override
public List<FilterElement<?>> getFilterElements()
/**
* Key extractor function for decode events to extract the decode event type
*/
public static class EventKeyExtractor implements Function<IDecodeEvent,DecodeEventType>
{
return new ArrayList<>(mElements.values());
@Override
public DecodeEventType apply(IDecodeEvent iDecodeEvent)
{
return iDecodeEvent.getEventType();
}
}
}

View File

@@ -1,84 +1,103 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event.filter;
import com.jidesoft.swing.JideButton;
import io.github.dsheirer.filter.FilterEditorPanel;
import io.github.dsheirer.filter.FilterSet;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import net.miginfocom.swing.MigLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
/**
* Event filter button that includes split button functionality to allow user to select filter items.
*
* @param <T> type of filter.
*/
public class EventFilterButton<T> extends JideButton
{
private static final long serialVersionUID = 1L;
/**
* Constructs an instance
* @param dialogTitle for the dialog/panel that appears
* @param filterSet to include in the editor panel.
*/
public EventFilterButton(String dialogTitle, FilterSet<T> filterSet)
{
this("Filter", dialogTitle, filterSet);
}
/**
* Constructs an instance
* @param buttonLabel to use on the button
* @param dialogTitle for the dialog/panel that appears
* @param filterSet to include in the editor panel.
*/
public EventFilterButton(String buttonLabel, String dialogTitle, FilterSet<T> filterSet)
{
super(buttonLabel);
addActionListener(
new EventFilterActionHandler(dialogTitle, filterSet)
);
addActionListener(new EventFilterActionHandler(dialogTitle, filterSet));
}
/**
* Action handler for the button
*/
public class EventFilterActionHandler implements ActionListener
{
private String title;
private FilterSet<T> filterSet;
private String mTitle;
private FilterSet<T> mFilterSet;
public EventFilterActionHandler( String title, FilterSet<T> filterSet)
/**
* Constructs an instance
* @param title for this panel
* @param filterSet to edit
*/
public EventFilterActionHandler(String title, FilterSet<T> filterSet)
{
this.title = title;
this.filterSet = filterSet;
mTitle = title;
mFilterSet = filterSet;
}
@Override
public void actionPerformed(ActionEvent e)
{
final JFrame editor = new JFrame();
editor.setTitle(title);
editor.setTitle(mTitle);
editor.setLocationRelativeTo(EventFilterButton.this);
editor.setSize(600, 400);
editor.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
editor.setLayout(new MigLayout("", "[grow,fill]",
"[grow,fill][][]"));
FilterEditorPanel<T> panel = new FilterEditorPanel<T>(filterSet);
editor.setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][][]"));
FilterEditorPanel<T> panel = new FilterEditorPanel<T>(mFilterSet);
JScrollPane scroller = new JScrollPane(panel);
scroller.setViewportView(panel);
editor.add(scroller, "wrap");
editor.add(new JLabel("Right-click to select/deselect all nodes"), "wrap");
JButton close = new JButton("Close");
close.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
editor.dispose();
}
});
close.addActionListener(e1 -> editor.dispose());
editor.add(close);
EventQueue.invokeLater(new Runnable()
{
public void run()
{
editor.setVisible(true);
}
});
EventQueue.invokeLater(() -> editor.setVisible(true));
}
}
}

View File

@@ -1,8 +0,0 @@
package io.github.dsheirer.module.decode.event.filter;
import io.github.dsheirer.filter.FilterSet;
public interface EventFilterProvider<T> {
FilterSet<T> getFilterSet();
void setFilterSet(FilterSet<T> filterSet);
}

View File

@@ -1,17 +0,0 @@
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<IDecodeEvent>
{
public LastHeardFilterSet(AliasModel aliasModel, IconModel iconModel)
{
super("Last Heard To Channel");
addFilter(new LastHeardIconFilter("Icon", aliasModel, iconModel));
}
}

View File

@@ -1,77 +0,0 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<IDecodeEvent> implements IDecodeEventListener
{
/**
* Constructs an instance
*/
public LastHeardHistory(int historySize)
{
super(historySize);
}
/**
* Implements the IDecodeEventListener interface - delegates to receive(event) method.
*/
@Override
public Listener<IDecodeEvent> 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));
}
}

View File

@@ -1,97 +0,0 @@
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<IDecodeEvent>
{
private AliasModel mAliasModel;
private Map<String, FilterElement<String>> mElements = new HashMap<>();
public LastHeardIconFilter(String name, AliasModel aliasModel, IconModel iconModel)
{
super(name);
this.mAliasModel = aliasModel;
ObservableList<Icon> 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<FilterElement<String>> matchAliasToFilter(IDecodeEvent iDecodeEvent)
{
IdentifierCollection identifierCollection = iDecodeEvent.getIdentifierCollection();
List<Identifier> 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<Alias> 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<FilterElement<?>> getFilterElements()
{
return new ArrayList<>(mElements.values());
}
}

View File

@@ -1,292 +0,0 @@
/*
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
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<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
{
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<IDecodeEvent> mEvents = new ArrayList<>();
protected FilterSet<IDecodeEvent> 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<IDecodeEvent> getEvents()
{
return new ArrayList<>(mEvents);
}
public void dispose()
{
MyEventBus.getGlobalEventBus().unregister(this);
Iterator<IDecodeEvent> 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<IDecodeEvent> 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<IDecodeEvent> getFilterSet() {
return mLastSeenFilterSet;
}
@Override
public void setFilterSet(FilterSet<IDecodeEvent> 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);
}
}

View File

@@ -1,482 +0,0 @@
/*
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
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<ProcessingChain>
{
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<IDecodeEvent> 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<IDecodeEvent> filterProvider = (EventFilterProvider<IDecodeEvent>) mTable.getModel();
FilterSet<IDecodeEvent> 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<Identifier> 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<Identifier> 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<Identifier> 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<Alias> 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);
}
}
}

View File

@@ -1,17 +0,0 @@
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<IDecodeEvent>
{
public LastSeenFilterSet(AliasModel aliasModel, IconModel iconModel)
{
super("Last Seen From Radio");
addFilter(new LastSeenIconFilter("Icon", aliasModel, iconModel));
}
}

View File

@@ -1,77 +0,0 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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<IDecodeEvent> implements IDecodeEventListener
{
/**
* Constructs an instance
*/
public LastSeenHistory(int historySize)
{
super(historySize);
}
/**
* Implements the IDecodeEventListener interface - delegates to receive(event) method.
*/
@Override
public Listener<IDecodeEvent> 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));
}
}

View File

@@ -1,47 +0,0 @@
/*
* *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
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;
}
}

View File

@@ -1,96 +0,0 @@
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<IDecodeEvent>
{
private AliasModel mAliasModel;
private Map<String, FilterElement<String>> mElements = new HashMap<>();
public LastSeenIconFilter(String name, AliasModel aliasModel, IconModel iconModel)
{
super(name);
this.mAliasModel = aliasModel;
ObservableList<Icon> 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<FilterElement<String>> matchAliasToFilter(IDecodeEvent iDecodeEvent)
{
IdentifierCollection identifierCollection = iDecodeEvent.getIdentifierCollection();
List<Identifier> 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<Alias> 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<FilterElement<?>> getFilterElements()
{
return new ArrayList<>(mElements.values());
}
}

View File

@@ -1,289 +0,0 @@
/*
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
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<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
{
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<IDecodeEvent> mEvents = new ArrayList<>();
protected FilterSet<IDecodeEvent> 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<IDecodeEvent> getEvents()
{
return new ArrayList<>(mEvents);
}
public void dispose()
{
MyEventBus.getGlobalEventBus().unregister(this);
Iterator<IDecodeEvent> 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<IDecodeEvent> 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<IDecodeEvent> getFilterSet() {
return mLastSeenFilterSet;
}
@Override
public void setFilterSet(FilterSet<IDecodeEvent> 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);
}
}

View File

@@ -1,482 +0,0 @@
/*
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
*
*
*/
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<ProcessingChain>
{
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<IDecodeEvent> 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<IDecodeEvent> filterProvider = (EventFilterProvider<IDecodeEvent>) mTable.getModel();
FilterSet<IDecodeEvent> 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<Identifier> 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<Identifier> 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<Identifier> 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<Alias> 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);
}
}
}

View File

@@ -2,24 +2,19 @@
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.fleetsync2;
@@ -125,10 +120,8 @@ public class Fleetsync2DecoderState extends DecoderState
broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.DATA));
break;
case GPS:
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(fleetsync.getTimestamp())
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, fleetsync.getTimestamp())
.channel(getCurrentChannel())
.eventType(DecodeEventType.GPS)
.eventDescription(DecodeEventType.GPS.toString())
.details(fleetsync.toString())
.identifiers(getIdentifierCollection().copyOf())
.protocol(Protocol.FLEETSYNC)
@@ -143,11 +136,9 @@ public class Fleetsync2DecoderState extends DecoderState
}
private DecodeEvent getDecodeEvent(Fleetsync2Message fleetsync, DecodeEventType eventType) {
return DecodeEvent.builder(fleetsync.getTimestamp())
return DecodeEvent.builder(eventType, fleetsync.getTimestamp())
.channel(getCurrentChannel())
.eventType(eventType)
.eventDescription(fleetsync.getMessageType().toString())
.details(fleetsync.toString())
.details(fleetsync.getMessageType() + " " + fleetsync)
.identifiers(getIdentifierCollection().copyOf())
.protocol(Protocol.FLEETSYNC)
.build();

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.fleetsync2;
@@ -24,52 +23,63 @@ import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.fleetsync2.message.Fleetsync2Message;
import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType;
import java.util.function.Function;
import java.util.*;
public class FleetsyncMessageFilter extends Filter<IMessage>
/**
* Filter for Fleetsync messages.
*/
public class FleetsyncMessageFilter extends Filter<IMessage,FleetsyncMessageType>
{
private Map<FleetsyncMessageType,FilterElement<FleetsyncMessageType>> mElements = new EnumMap<>(FleetsyncMessageType.class);
private final KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public FleetsyncMessageFilter()
{
super("Fleetsync Message Filter");
super("Fleetsync Messages");
for(FleetsyncMessageType type : FleetsyncMessageType.values())
{
if(type != FleetsyncMessageType.UNKNOWN)
{
mElements.put(type, new FilterElement<FleetsyncMessageType>(type));
}
add(new FilterElement<>(type));
}
}
@Override
public boolean passes(IMessage message)
{
if(mEnabled && canProcess(message))
{
Fleetsync2Message fleet = (Fleetsync2Message)message;
if(mElements.containsKey(fleet.getMessageType()))
{
return mElements.get(fleet.getMessageType()).isEnabled();
}
}
return false;
}
/**
* Limits processing to only fleetsync messages.
* @param message to test
* @return true if message type is correct.
*/
@Override
public boolean canProcess(IMessage message)
{
return message instanceof Fleetsync2Message;
return message instanceof Fleetsync2Message && super.canProcess(message);
}
/**
* Key extractor for fleetsync messages
* @return key extractor
*/
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, FleetsyncMessageType> getKeyExtractor()
{
return new ArrayList<FilterElement<?>>(mElements.values());
return mKeyExtractor;
}
/**
* Key extractor for Fleetsync 2 Messages.
*/
private class KeyExtractor implements Function<IMessage, FleetsyncMessageType>
{
@Override
public FleetsyncMessageType apply(IMessage message)
{
if(message instanceof Fleetsync2Message f2m)
{
return f2m.getMessageType();
}
return null;
}
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.lj1200;
@@ -31,14 +28,14 @@ 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;
import io.github.dsheirer.protocol.Protocol;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LJ1200DecoderState extends DecoderState
{
@@ -73,12 +70,11 @@ public class LJ1200DecoderState extends DecoderState
ic.remove(IdentifierClass.USER);
ic.update(lj.getIdentifiers());
DecodeEvent event = DecodeEvent.builder(System.currentTimeMillis())
DecodeEvent event = DecodeEvent.builder(DecodeEventType.DATA_PACKET, System.currentTimeMillis())
.protocol(Protocol.LOJACK)
.identifiers(ic)
.eventType(DecodeEventType.DATA_PACKET)
.channel(getCurrentChannel())
.details("LOJACK")
.eventDescription(lj.toString())
.details("LOJACK " + lj)
.build();
broadcast(event);
@@ -93,12 +89,11 @@ public class LJ1200DecoderState extends DecoderState
ic.remove(IdentifierClass.USER);
ic.update(transponder.getIdentifiers());
DecodeEvent transponderEvent = DecodeEvent.builder(System.currentTimeMillis())
DecodeEvent transponderEvent = DecodeEvent.builder(DecodeEventType.GPS, System.currentTimeMillis())
.protocol(Protocol.LOJACK)
.identifiers(ic)
.eventType(DecodeEventType.GPS)
.channel(getCurrentChannel())
.details("LOJACK TRANSPONDER")
.eventDescription(transponder.toString())
.details("LOJACK TRANSPONDER " + transponder)
.build();
broadcast(transponderEvent);
@@ -117,7 +112,7 @@ public class LJ1200DecoderState extends DecoderState
{
sb.append("Transponder Addresses:\n");
List<String> addresses = new ArrayList<String>(mAddresses);
List<String> addresses = new ArrayList<>(mAddresses);
Collections.sort(addresses);

View File

@@ -1,52 +1,66 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 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.
* 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 <http://www.gnu.org/licenses/>
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.lj1200;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import java.util.function.Function;
import java.util.Collections;
import java.util.List;
public class LJ1200MessageFilter extends Filter<IMessage>
/**
* Filter for LJ1200 messages
*/
public class LJ1200MessageFilter extends Filter<IMessage,String>
{
private static final String LJ1200_KEY = "LJ-1200";
private final LJ1200KeyExtractor mKeyExtractor = new LJ1200KeyExtractor();
/**
* Constructor
*/
public LJ1200MessageFilter()
{
super("LJ-1200 Message Filter");
}
@Override
public boolean passes(IMessage message)
{
return mEnabled && canProcess(message);
super("LJ-1200 Messages");
add(new FilterElement<>(LJ1200_KEY));
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof LJ1200Message || message instanceof LJ1200TransponderMessage;
return (message instanceof LJ1200Message || message instanceof LJ1200TransponderMessage);
}
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, String> getKeyExtractor()
{
return Collections.EMPTY_LIST;
return mKeyExtractor;
}
/**
* Key extractor
*/
public class LJ1200KeyExtractor implements Function<IMessage,String>
{
@Override
public String apply(IMessage message)
{
return LJ1200_KEY;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,23 +14,27 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ltrnet;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.protocol.Protocol;
/**
* LTRNet decode event
*/
public class LTRNetDecodeEvent extends DecodeEvent
{
/**
* Constructs an LTR NET decode event
* @param start
*/
public LTRNetDecodeEvent(long start)
public LTRNetDecodeEvent(DecodeEventType decodeEventType, long start)
{
super(start);
super(decodeEventType, start);
setProtocol(Protocol.LTR_NET);
}
@@ -40,9 +43,9 @@ public class LTRNetDecodeEvent extends DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
{
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
decodeEventBuilder.protocol(Protocol.LTR_NET);
return decodeEventBuilder;
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ltrnet;
@@ -61,9 +58,6 @@ import io.github.dsheirer.module.decode.ltrnet.message.osw.SystemIdle;
import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyHigh;
import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyLow;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -72,6 +66,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LTRNetDecoderState extends DecoderState
{
@@ -167,9 +163,24 @@ public class LTRNetDecoderState extends DecoderState
if(mCurrentCallTalkgroup == null || !mCurrentCallTalkgroup.equals(talkgroup))
{
DecodeEventType decodeEventType = null;
if(talkgroup.getTalkgroup() == 253)
{
decodeEventType = DecodeEventType.REGISTER;
}
else if(talkgroup.getTalkgroup() == 254)
{
decodeEventType = DecodeEventType.STATION_ID;
}
else
{
decodeEventType = DecodeEventType.CALL;
}
getIdentifierCollection().remove(IdentifierClass.USER);
getIdentifierCollection().update(talkgroup);
mCurrentCallEvent = LTRNetDecodeEvent.builder(timestamp)
mCurrentCallEvent = LTRNetDecodeEvent.builder(decodeEventType, timestamp)
.channel(getCurrentChannel())
.identifiers(getIdentifierCollection().copyOf())
.build();
@@ -181,20 +192,14 @@ 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));
}
@@ -292,77 +297,59 @@ public class LTRNetDecoderState extends DecoderState
switch(((LtrNetMessage)message).getLtrNetMessageType())
{
case ISW_CALL_END:
if(message instanceof IswCallEnd)
if(message instanceof IswCallEnd callEnd)
{
IswCallEnd callEnd = (IswCallEnd)message;
mTalkgroups.add(callEnd.getTalkgroup());
processCallEnd(callEnd.getChannel(), callEnd.getTalkgroup(), callEnd.getTimestamp());
}
break;
case ISW_CALL_START:
if(message instanceof IswCallStart)
if(message instanceof IswCallStart callStart)
{
IswCallStart callStart = (IswCallStart)message;
mTalkgroups.add(callStart.getTalkgroup());
processCallStart(callStart.getChannel(), callStart.getTalkgroup(), callStart.getTimestamp(),
MessageDirection.ISW);
}
break;
case ISW_REGISTRATION_REQUEST_ESN_HIGH:
if(message instanceof RegistrationRequestEsnHigh)
if(message instanceof RegistrationRequestEsnHigh registrationRequestEsn)
{
RegistrationRequestEsnHigh registrationRequestEsn = (RegistrationRequestEsnHigh)message;
if(registrationRequestEsn.isCompleteEsn())
{
getIdentifierCollection().update(registrationRequestEsn.getESN());
mESNIdentifiers.add(registrationRequestEsn.getESN());
}
broadcast(getDecodeEvent(
message, DecodeEventType.REQUEST,
"Registration Request", registrationRequestEsn.toString()));
broadcast(getDecodeEvent(message, DecodeEventType.REQUEST, "REGISTER: " + registrationRequestEsn));
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
}
break;
case ISW_REGISTRATION_REQUEST_ESN_LOW:
if(message instanceof RegistrationRequestEsnLow)
if(message instanceof RegistrationRequestEsnLow registrationRequestEsn)
{
RegistrationRequestEsnLow registrationRequestEsn = (RegistrationRequestEsnLow)message;
if(registrationRequestEsn.isCompleteEsn())
{
getIdentifierCollection().update(registrationRequestEsn.getESN());
mESNIdentifiers.add(registrationRequestEsn.getESN());
}
broadcast(getDecodeEvent(
message, DecodeEventType.REQUEST,
"Registration Request", registrationRequestEsn.toString()));
broadcast(getDecodeEvent(message, DecodeEventType.REQUEST, "REGISTER: " + registrationRequestEsn));
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
}
break;
case ISW_REQUEST_ACCESS:
if(message instanceof RequestAccess)
if(message instanceof RequestAccess requestAccess)
{
RequestAccess requestAccess = (RequestAccess)message;
getIdentifierCollection().update(requestAccess.getTalkgroup());
mTalkgroups.add(requestAccess.getTalkgroup());
broadcast(getDecodeEvent(
message, DecodeEventType.REQUEST,
"Access Request", requestAccess.toString()));
broadcast(getDecodeEvent(message, DecodeEventType.REQUEST, "ACCESS: " + requestAccess));
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
}
break;
case ISW_UNIQUE_ID:
if(message instanceof IswUniqueId)
if(message instanceof IswUniqueId iswUniqueId)
{
IswUniqueId iswUniqueId = (IswUniqueId)message;
getIdentifierCollection().update(iswUniqueId.getUniqueID());
mLtrNetRadioIdentifiers.add(iswUniqueId.getUniqueID());
updateCurrentCallIdentifiers();
@@ -373,99 +360,90 @@ public class LTRNetDecoderState extends DecoderState
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_CALL_END:
if(message instanceof OswCallEnd)
if(message instanceof OswCallEnd callEnd)
{
OswCallEnd callEnd = (OswCallEnd)message;
mTalkgroups.add(callEnd.getTalkgroup());
processCallEnd(callEnd.getChannel(), callEnd.getTalkgroup(), callEnd.getTimestamp());
}
break;
case OSW_CALL_START:
if(message instanceof OswCallStart)
if(message instanceof OswCallStart callStart)
{
OswCallStart callStart = (OswCallStart)message;
mTalkgroups.add(callStart.getTalkgroup());
processCallStart(callStart.getChannel(), callStart.getTalkgroup(), callStart.getTimestamp(),
MessageDirection.OSW);
}
break;
case OSW_CHANNEL_MAP_HIGH:
if(message instanceof ChannelMapHigh)
if(message instanceof ChannelMapHigh channelMapHigh)
{
mChannelMapHigh = (ChannelMapHigh)message;
mChannelMapHigh = channelMapHigh;
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_CHANNEL_MAP_LOW:
if(message instanceof ChannelMapLow)
if(message instanceof ChannelMapLow channelMapLow)
{
mChannelMapLow = (ChannelMapLow)message;
mChannelMapLow = channelMapLow;
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_SYSTEM_IDLE:
if(message instanceof SystemIdle)
if(message instanceof SystemIdle systemIdle)
{
setCurrentChannelNumber(((SystemIdle)message).getChannel());
setCurrentChannelNumber(systemIdle.getChannel());
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_NEIGHBOR_ID:
if(message instanceof NeighborId)
if(message instanceof NeighborId neighborId)
{
NeighborId neighborId = (NeighborId)message;
mNeighborMap.put(neighborId.getNeighborRank(), neighborId);
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_RECEIVE_FREQUENCY_HIGH:
if(message instanceof ReceiveFrequencyHigh)
if(message instanceof ReceiveFrequencyHigh receiveFrequency)
{
ReceiveFrequencyHigh receiveFrequency = (ReceiveFrequencyHigh)message;
updateReceiveFrequency(receiveFrequency.getChannel(), receiveFrequency.getFrequency());
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_RECEIVE_FREQUENCY_LOW:
if(message instanceof ReceiveFrequencyLow)
if(message instanceof ReceiveFrequencyLow receiveFrequency)
{
ReceiveFrequencyLow receiveFrequency = (ReceiveFrequencyLow)message;
updateReceiveFrequency(receiveFrequency.getChannel(), receiveFrequency.getFrequency());
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_REGISTRATION_ACCEPT:
if(message instanceof RegistrationAccept)
if(message instanceof RegistrationAccept registrationAccept)
{
RegistrationAccept registrationAccept = (RegistrationAccept)message;
mLtrNetRadioIdentifiers.add(registrationAccept.getUniqueID());
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
ic.remove(IdentifierClass.USER);
ic.update(message.getIdentifiers());
broadcast(getDecodeEvent(message, DecodeEventType.RESPONSE,
"Registration Accept", registrationAccept.toString()));
broadcast(getDecodeEvent(message, DecodeEventType.RESPONSE, "REGISTRATION ACCEPT: " + registrationAccept));
}
break;
case OSW_SITE_ID:
if(message instanceof SiteId)
if(message instanceof SiteId siteId)
{
mCurrentSite = (SiteId)message;
mCurrentSite = siteId;
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_TRANSMIT_FREQUENCY_HIGH:
if(message instanceof TransmitFrequencyHigh)
if(message instanceof TransmitFrequencyHigh transmitFrequency)
{
TransmitFrequencyHigh transmitFrequency = (TransmitFrequencyHigh)message;
updateTransmitFrequency(transmitFrequency.getChannel(), transmitFrequency.getFrequency());
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
break;
case OSW_TRANSMIT_FREQUENCY_LOW:
if(message instanceof TransmitFrequencyLow)
if(message instanceof TransmitFrequencyLow transmitFrequency)
{
TransmitFrequencyLow transmitFrequency = (TransmitFrequencyLow)message;
updateTransmitFrequency(transmitFrequency.getChannel(), transmitFrequency.getFrequency());
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
@@ -477,24 +455,22 @@ 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)
private DecodeEvent getDecodeEvent(IMessage message, DecodeEventType decodeEventType, String details)
{
return LTRNetDecodeEvent.builder(decodeEventType, message.getTimestamp())
.details(details)
.identifiers(getIdentifierCollection().copyOf())
.channel(getCurrentChannel())
.build();
}
private DecodeEvent getDecodeEvent(LTRTalkgroup talkgroup, int channel, long timestamp, DecodeEventType decodeEventType) {
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())
return LTRNetDecodeEvent.builder(decodeEventType, timestamp)
.channel(mChannelMap.get(channel))
.identifiers(ic)
.build();

View File

@@ -1,52 +1,85 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ltrnet;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage;
import io.github.dsheirer.module.decode.ltrstandard.LtrStandardMessageType;
import java.util.function.Function;
import java.util.*;
public class LTRNetMessageFilter extends Filter<IMessage>
/**
* Filter for LTR-Net messages
*/
public class LTRNetMessageFilter extends Filter<IMessage,LtrNetMessageType>
{
private Map<LtrNetMessageType, FilterElement<LtrNetMessageType>> mElements = new EnumMap<>(LtrNetMessageType.class);
private final KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public LTRNetMessageFilter()
{
super("LTR-Net Message Filter");
super("LTR-Net Messages");
for(LtrNetMessageType type: LtrNetMessageType.values())
{
mElements.put(type, new FilterElement<>(type));
add(new FilterElement<>(type));
}
}
@Override
public boolean passes(IMessage message)
{
if(mEnabled && canProcess(message))
{
LtrNetMessage ltr = (LtrNetMessage) message;
if(mElements.containsKey(ltr.getLtrNetMessageType()))
{
return mElements.get(ltr.getLtrNetMessageType()).isEnabled();
}
}
return false;
}
/**
* Tests for instance of LTR-Net message
* @param message to test
* @return true if correct type.
*/
@Override
public boolean canProcess(IMessage message)
{
return message instanceof LtrNetMessage;
return message instanceof LtrNetMessage && super.canProcess(message);
}
/**
* Key extractor for LTR-Net messages
* @return extractor
*/
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, LtrNetMessageType> getKeyExtractor()
{
return new ArrayList<FilterElement<?>>(mElements.values());
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,LtrNetMessageType>
{
@Override
public LtrNetMessageType apply(IMessage message)
{
if(message instanceof LtrNetMessage ltr)
{
return ltr.getLtrNetMessageType();
}
return null;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,23 +14,27 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ltrstandard;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.protocol.Protocol;
/**
* LTR decode event
*/
public class LTRStandardDecodeEvent extends DecodeEvent
{
/**
* Constructs an LTR Standard decode event
* @param start
*/
public LTRStandardDecodeEvent(long start)
public LTRStandardDecodeEvent(DecodeEventType decodeEventType, long start)
{
super(start);
super(decodeEventType, start);
setProtocol(Protocol.LTR);
}
@@ -40,9 +43,9 @@ public class LTRStandardDecodeEvent extends DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
{
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
decodeEventBuilder.protocol(Protocol.LTR);
return decodeEventBuilder;
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ltrstandard;
@@ -42,9 +39,6 @@ import io.github.dsheirer.module.decode.ltrstandard.message.CallEnd;
import io.github.dsheirer.module.decode.ltrstandard.message.Idle;
import io.github.dsheirer.module.decode.ltrstandard.message.LTRMessage;
import io.github.dsheirer.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -52,6 +46,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LTRStandardDecoderState extends DecoderState
{
@@ -82,10 +78,8 @@ public class LTRStandardDecoderState extends DecoderState
switch(((LTRMessage)message).getMessageType())
{
case CALL:
if(message instanceof Call)
if(message instanceof Call start)
{
Call start = (Call)message;
int channel = start.getChannel();
setChannelNumber(channel);
mLCNTracker.processFreeChannel(start.getFree());
@@ -98,11 +92,9 @@ public class LTRStandardDecoderState extends DecoderState
mCurrentTalkgroup = start.getTalkgroup();
getIdentifierCollection().remove(IdentifierClass.USER);
getIdentifierCollection().update(start.getTalkgroup());
mCurrentCallEvent = LTRStandardDecodeEvent.builder(start.getTimestamp())
mCurrentCallEvent = LTRStandardDecodeEvent.builder(DecodeEventType.CALL, start.getTimestamp())
.identifiers(getIdentifierCollection().copyOf())
.channel(getCurrentChannel())
.eventType(DecodeEventType.CALL)
.eventDescription("Call")
.build();
}
else
@@ -115,10 +107,8 @@ public class LTRStandardDecoderState extends DecoderState
}
break;
case CALL_END:
if(message instanceof CallEnd)
if(message instanceof CallEnd end)
{
CallEnd end = (CallEnd)message;
mCurrentTalkgroup = null;
//Home channel is 31 for call end -- use the free channel as the call end channel
@@ -136,10 +126,10 @@ public class LTRStandardDecoderState extends DecoderState
}
break;
case IDLE:
if(message instanceof Idle)
if(message instanceof Idle idle)
{
mCurrentTalkgroup = null;
mLCNTracker.processCallChannel(((Idle)message).getChannel());
mLCNTracker.processCallChannel(idle.getChannel());
}
break;
default:

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.ltrstandard;
@@ -24,47 +23,53 @@ import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.ltrstandard.message.LTRMessage;
import java.util.function.Function;
import java.util.*;
public class LTRStandardMessageFilter extends Filter<IMessage>
/**
* Filter for LTR standard messages.
*/
public class LTRStandardMessageFilter extends Filter<IMessage,LtrStandardMessageType>
{
private Map<LtrStandardMessageType,FilterElement<LtrStandardMessageType>> mElements = new EnumMap<>(LtrStandardMessageType.class);
private final KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public LTRStandardMessageFilter()
{
super("LTR Message Filter");
super("LTR Messages");
mElements.put(LtrStandardMessageType.CALL, new FilterElement<>(LtrStandardMessageType.CALL));
mElements.put(LtrStandardMessageType.CALL_END, new FilterElement<>(LtrStandardMessageType.CALL_END));
mElements.put(LtrStandardMessageType.IDLE, new FilterElement<>(LtrStandardMessageType.IDLE));
mElements.put(LtrStandardMessageType.UNKNOWN, new FilterElement<>(LtrStandardMessageType.UNKNOWN));
}
@Override
public boolean passes(IMessage message)
{
if(mEnabled && canProcess(message))
for(LtrStandardMessageType type: LtrStandardMessageType.values())
{
LTRMessage ltr = (LTRMessage)message;
if(mElements.containsKey(ltr.getMessageType()))
{
return mElements.get(ltr.getMessageType()).isEnabled();
}
add(new FilterElement<>(type));
}
return false;
}
public boolean canProcess(IMessage message)
{
return message instanceof LTRMessage;
return message instanceof LTRMessage && super.canProcess(message);
}
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, LtrStandardMessageType> getKeyExtractor()
{
return new ArrayList<FilterElement<?>>(mElements.values());
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,LtrStandardMessageType>
{
@Override
public LtrStandardMessageType apply(IMessage message)
{
if(message instanceof LTRMessage ltr)
{
return ltr.getMessageType();
}
return null;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,23 +14,28 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.mdc1200;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.protocol.Protocol;
/**
* MDC-1200 decode event
*/
public class MDCDecodeEvent extends DecodeEvent
{
/**
* Constucts a P25 decode event
* @param start
* Constucts an MDC1200 decode event
* @param decodeEventType for the event
* @param start for the event
*/
public MDCDecodeEvent(long start)
public MDCDecodeEvent(DecodeEventType decodeEventType, long start)
{
super(start);
super(decodeEventType, start);
setProtocol(Protocol.MDC1200);
}
@@ -40,9 +44,9 @@ public class MDCDecodeEvent extends DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
{
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
decodeEventBuilder.protocol(Protocol.MDC1200);
return decodeEventBuilder;
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * 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 <http://www.gnu.org/licenses/>
* * *****************************************************************************
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.mdc1200;
@@ -31,7 +28,6 @@ import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.mdc1200.identifier.MDC1200Identifier;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
@@ -114,9 +110,7 @@ public class MDCDecoderState extends DecoderState
ic.remove(IdentifierClass.USER);
ic.update(message.getIdentifiers());
broadcast(MDCDecodeEvent.builder(mdc.getTimestamp())
.eventType(getDecodeEventType(type))
.eventDescription(type.getLabel())
broadcast(MDCDecodeEvent.builder(getDecodeEventType(type), mdc.getTimestamp())
.details(mdc.toString())
.identifiers(ic)
.build());

View File

@@ -1,55 +1,75 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.mdc1200;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.module.decode.mpt1327.MPT1327Message;
import java.util.function.Function;
import java.util.*;
public class MDCMessageFilter extends Filter<IMessage>
/**
* Message filter for MDC-1200 messages
*/
public class MDCMessageFilter extends Filter<IMessage,MDCMessageType>
{
private Map<MDCMessageType, FilterElement<MDCMessageType>> mElements = new EnumMap<>(MDCMessageType.class);
private final KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public MDCMessageFilter()
{
super("MDC-1200 Message Filter");
super("MDC-1200 Messages");
for(MDCMessageType type : MDCMessageType.values())
{
if(type != MDCMessageType.UNKNOWN)
{
mElements.put(type, new FilterElement<MDCMessageType>(type));
}
add(new FilterElement<>(type));
}
}
@Override
public boolean passes(IMessage message)
{
if(mEnabled && canProcess(message))
{
MDCMessage mdc = (MDCMessage) message;
if(mElements.containsKey(mdc.getMessageType()))
{
return mElements.get(mdc.getMessageType()).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof MDCMessage;
return message instanceof MDCMessage && super.canProcess(message);
}
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, MDCMessageType> getKeyExtractor()
{
return new ArrayList<FilterElement<?>>(mElements.values());
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,MDCMessageType>
{
@Override
public MDCMessageType apply(IMessage message)
{
if(message instanceof MDCMessage mdc)
{
return mdc.getMessageType();
}
return null;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,21 +14,23 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
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;
public class MPT1327ChannelGrantEvent extends P25DecodeEvent
/**
* MPT1327 Channel Grant Event
*/
public class MPT1327ChannelGrantEvent extends MPT1327DecodeEvent
{
public MPT1327ChannelGrantEvent(long timestamp)
public MPT1327ChannelGrantEvent(DecodeEventType decodeEventType, long timestamp)
{
super(timestamp);
super(decodeEventType, timestamp);
}
/**
@@ -37,9 +38,9 @@ public class MPT1327ChannelGrantEvent extends P25DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static MPT1327ChannelGrantDecodeEventBuilder mpt1327Builder(long timeStart)
public static MPT1327ChannelGrantDecodeEventBuilder mpt1327Builder(DecodeEventType decodeEventType, long timeStart)
{
return new MPT1327ChannelGrantDecodeEventBuilder(timeStart);
return new MPT1327ChannelGrantDecodeEventBuilder(decodeEventType, timeStart);
}
/**
@@ -49,7 +50,6 @@ public class MPT1327ChannelGrantEvent extends P25DecodeEvent
{
protected long mTimeStart;
protected long mDuration;
protected String mEventDescription;
protected DecodeEventType mDecodeEventType;
protected IdentifierCollection mIdentifierCollection;
protected IChannelDescriptor mChannelDescriptor;
@@ -60,8 +60,9 @@ public class MPT1327ChannelGrantEvent extends P25DecodeEvent
*
* @param timeStart
*/
public MPT1327ChannelGrantDecodeEventBuilder(long timeStart)
public MPT1327ChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
{
mDecodeEventType = decodeEventType;
mTimeStart = timeStart;
}
@@ -95,25 +96,6 @@ 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
*/
public MPT1327ChannelGrantDecodeEventBuilder eventDescription(String description)
{
mEventDescription = description;
return this;
}
/**
* Sets the identifier collection.
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
@@ -140,13 +122,11 @@ public class MPT1327ChannelGrantEvent extends P25DecodeEvent
*/
public MPT1327ChannelGrantEvent build()
{
MPT1327ChannelGrantEvent decodeEvent = new MPT1327ChannelGrantEvent(mTimeStart);
MPT1327ChannelGrantEvent decodeEvent = new MPT1327ChannelGrantEvent(mDecodeEventType, mTimeStart);
decodeEvent.setProtocol(Protocol.MPT1327);
decodeEvent.setChannelDescriptor(mChannelDescriptor);
decodeEvent.setDetails(mDetails);
decodeEvent.setDuration(mDuration);
decodeEvent.setEventType(mDecodeEventType);
decodeEvent.setEventDescription(mEventDescription);
decodeEvent.setIdentifierCollection(mIdentifierCollection);
return decodeEvent;
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,23 +14,27 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.mpt1327;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.protocol.Protocol;
/**
* MPT1327 Decode Event
*/
public class MPT1327DecodeEvent extends DecodeEvent
{
/**
* Constructs a MPT1327 decode event
* @param start
*/
public MPT1327DecodeEvent(long start)
public MPT1327DecodeEvent(DecodeEventType decodeEventType, long start)
{
super(start);
super(decodeEventType, start);
setProtocol(Protocol.MPT1327);
}
@@ -40,9 +43,9 @@ public class MPT1327DecodeEvent extends DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
{
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
decodeEventBuilder.protocol(Protocol.MPT1327);
return decodeEventBuilder;
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,14 +33,13 @@ 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.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MPT1327DecoderState extends DecoderState
{
@@ -198,9 +197,7 @@ 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())
return MPT1327DecodeEvent.builder(decodeEventType, message.getTimestamp())
.details(details)
.identifiers(ic)
.build();

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.mpt1327;
@@ -23,56 +22,55 @@ package io.github.dsheirer.module.decode.mpt1327;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.MessageType;
import java.util.function.Function;
import java.util.*;
public class MPT1327MessageFilter extends Filter<IMessage>
/**
* Filter for MPT1327 messages
*/
public class MPT1327MessageFilter extends Filter<IMessage, MPT1327Message.MPTMessageType>
{
private Map<MPT1327Message.MPTMessageType, FilterElement<MPT1327Message.MPTMessageType>> mFilterElements = new EnumMap<>(MPT1327Message.MPTMessageType.class);
private final KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public MPT1327MessageFilter()
{
super("MPT1327 Message Type Filter");
super("MPT1327 Messages");
for(MPT1327Message.MPTMessageType type : MPT1327Message.MPTMessageType.values())
{
if(type != MPT1327Message.MPTMessageType.UNKN)
{
mFilterElements.put(type, new FilterElement<MPT1327Message.MPTMessageType>(type));
}
add(new FilterElement<>(type));
}
}
@Override
public boolean passes(IMessage message)
{
if(mEnabled && canProcess(message))
{
MPT1327Message mpt = (MPT1327Message) message;
FilterElement<MPT1327Message.MPTMessageType> element =
mFilterElements.get(mpt.getMessageType());
if(element != null)
{
return element.isEnabled();
}
}
return false;
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof MPT1327Message;
return message instanceof MPT1327Message && super.canProcess(message);
}
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, MPT1327Message.MPTMessageType> getKeyExtractor()
{
return new ArrayList<FilterElement<?>>(mFilterElements.values());
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage, MPT1327Message.MPTMessageType>
{
@Override
public MPT1327Message.MPTMessageType apply(IMessage message)
{
if(message instanceof MPT1327Message mpt)
{
return mpt.getMessageType();
}
return null;
}
}
}

View File

@@ -104,10 +104,9 @@ public class MPT1327TrafficChannelManager extends TrafficChannelManager implemen
}
}
MPT1327ChannelGrantEvent channelGrantEvent = MPT1327ChannelGrantEvent.mpt1327Builder(mpt1327Message.getTimestamp())
MPT1327ChannelGrantEvent channelGrantEvent = MPT1327ChannelGrantEvent
.mpt1327Builder(DecodeEventType.CALL, mpt1327Message.getTimestamp())
.channel(mpt1327Channel)
.eventType(DecodeEventType.CALL)
.eventDescription("Call")
.details("Traffic Channel Grant")
.identifiers(identifierCollection)
.build();
@@ -125,7 +124,7 @@ public class MPT1327TrafficChannelManager extends TrafficChannelManager implemen
if(trafficChannel == null)
{
channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
channelGrantEvent.setEventDescription("Detect:" + channelGrantEvent.getEventDescription());
channelGrantEvent.setDetails("Detect:" + channelGrantEvent.getDetails());
return;
}
@@ -152,10 +151,8 @@ public class MPT1327TrafficChannelManager extends TrafficChannelManager implemen
DecodeConfiguration decodeConfiguration = parentChannel.getDecodeConfiguration();
List<Channel> trafficChannelList = new ArrayList<>();
if(decodeConfiguration instanceof DecodeConfigMPT1327)
if(decodeConfiguration instanceof DecodeConfigMPT1327 decodeConfigMPT1327)
{
DecodeConfigMPT1327 decodeConfigMPT1327 = (DecodeConfigMPT1327)decodeConfiguration;
int maxTrafficChannels = decodeConfigMPT1327.getTrafficChannelPoolSize();
for(int x = 0; x < maxTrafficChannels; x++)

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25;
@@ -29,9 +28,9 @@ public class P25ChannelGrantEvent extends P25DecodeEvent
{
private ServiceOptions mServiceOptions;
public P25ChannelGrantEvent(long timestamp)
public P25ChannelGrantEvent(DecodeEventType decodeEventType, long timestamp)
{
super(timestamp);
super(decodeEventType, timestamp);
}
/**
@@ -39,9 +38,9 @@ public class P25ChannelGrantEvent extends P25DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static P25ChannelGrantDecodeEventBuilder builder(long timeStart, ServiceOptions serviceOptions)
public static P25ChannelGrantDecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart, ServiceOptions serviceOptions)
{
return new P25ChannelGrantDecodeEventBuilder(timeStart, serviceOptions);
return new P25ChannelGrantDecodeEventBuilder(decodeEventType, timeStart, serviceOptions);
}
/**
@@ -76,7 +75,6 @@ public class P25ChannelGrantEvent extends P25DecodeEvent
{
protected long mTimeStart;
protected long mDuration;
protected String mEventDescription;
protected DecodeEventType mDecodeEventType;
protected IdentifierCollection mIdentifierCollection;
protected IChannelDescriptor mChannelDescriptor;
@@ -88,8 +86,9 @@ public class P25ChannelGrantEvent extends P25DecodeEvent
*
* @param timeStart
*/
public P25ChannelGrantDecodeEventBuilder(long timeStart, ServiceOptions serviceOptions)
public P25ChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart, ServiceOptions serviceOptions)
{
mDecodeEventType = decodeEventType;
mTimeStart = timeStart;
mServiceOptions = serviceOptions;
}
@@ -124,26 +123,6 @@ public class P25ChannelGrantEvent extends P25DecodeEvent
return this;
}
/**
* Sets the event description text
* @param description of the event
*/
public P25ChannelGrantDecodeEventBuilder eventDescription(String description)
{
mEventDescription = description;
return this;
}
/**
* Sets the {@link DecodeEventType}
* @param decodeEventType of the event
*/
public P25ChannelGrantDecodeEventBuilder eventType(DecodeEventType decodeEventType)
{
mDecodeEventType = decodeEventType;
return this;
}
/**
* Sets the identifier collection.
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
@@ -170,13 +149,11 @@ public class P25ChannelGrantEvent extends P25DecodeEvent
*/
public P25ChannelGrantEvent build()
{
P25ChannelGrantEvent decodeEvent = new P25ChannelGrantEvent(mTimeStart);
P25ChannelGrantEvent decodeEvent = new P25ChannelGrantEvent(mDecodeEventType, mTimeStart);
decodeEvent.setProtocol(Protocol.APCO25);
decodeEvent.setChannelDescriptor(mChannelDescriptor);
decodeEvent.setDetails(mDetails);
decodeEvent.setDuration(mDuration);
decodeEvent.setEventType(mDecodeEventType);
decodeEvent.setEventDescription(mEventDescription);
decodeEvent.setIdentifierCollection(mIdentifierCollection);
decodeEvent.setServiceOptions(mServiceOptions);
return decodeEvent;

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +14,13 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.protocol.Protocol;
public class P25DecodeEvent extends DecodeEvent
@@ -29,9 +29,9 @@ public class P25DecodeEvent extends DecodeEvent
* Constructs a P25 decode event
* @param start
*/
public P25DecodeEvent(long start)
public P25DecodeEvent(DecodeEventType decodeEventType, long start)
{
super(start);
super(decodeEventType, start);
setProtocol(Protocol.APCO25);
}
@@ -40,9 +40,9 @@ public class P25DecodeEvent extends DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
{
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
decodeEventBuilder.protocol(Protocol.APCO25);
return decodeEventBuilder;
}

View File

@@ -281,11 +281,9 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
{
event.end(timestamp);
P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions)
P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
.channel(apco25Channel)
.eventType(decodeEventType)
.eventDescription(decodeEventType.toString() + " - Continue")
.details("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : ""))
.details("CONTINUE - PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : ""))
.identifiers(identifierCollection)
.build();
@@ -306,7 +304,6 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
if(trafficChannel != null)
{
event.setEventDescription(decodeEventType.toString());
event.setDetails("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : ""));
event.setChannelDescriptor(apco25Channel);
broadcast(event);
@@ -327,10 +324,8 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
if(mIgnoreDataCalls && opcode.isDataChannelGrant())
{
P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions)
P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
.channel(apco25Channel)
.eventType(decodeEventType)
.eventDescription(decodeEventType.toString() + " - Ignored")
.details("DATA CALL IGNORED: " + (serviceOptions != null ? serviceOptions : ""))
.identifiers(identifierCollection)
.build();
@@ -340,10 +335,8 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
return;
}
P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions)
P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
.channel(apco25Channel)
.eventType(decodeEventType)
.eventDescription(decodeEventType.toString())
.details("PHASE 1 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : ""))
.identifiers(identifierCollection)
.build();
@@ -357,8 +350,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
if(trafficChannel == null)
{
channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored");
channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED");
return;
}
@@ -427,11 +419,9 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
{
event.end(timestamp);
P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions)
P25ChannelGrantEvent continuationGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
.channel(apco25Channel)
.eventType(decodeEventType)
.eventDescription(decodeEventType.toString() + " - Continue")
.details("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : ""))
.details("CONTINUE - PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : ""))
.identifiers(identifierCollection)
.build();
@@ -460,7 +450,6 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
if(trafficChannel != null)
{
event.setEventDescription(decodeEventType.toString());
event.setDetails("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : ""));
event.setChannelDescriptor(apco25Channel);
broadcast(event);
@@ -488,10 +477,8 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
if(mIgnoreDataCalls && opcode.isDataChannelGrant())
{
P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions)
P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
.channel(apco25Channel)
.eventType(decodeEventType)
.eventDescription(decodeEventType.toString() + " - Ignored")
.details("PHASE 2 DATA CALL IGNORED: " + (serviceOptions != null ? serviceOptions : ""))
.identifiers(identifierCollection)
.build();
@@ -502,10 +489,8 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
return;
}
P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(timestamp, serviceOptions)
P25ChannelGrantEvent channelGrantEvent = P25ChannelGrantEvent.builder(decodeEventType, timestamp, serviceOptions)
.channel(apco25Channel)
.eventType(decodeEventType)
.eventDescription(decodeEventType.toString())
.details("PHASE 2 CHANNEL GRANT " + (serviceOptions != null ? serviceOptions : ""))
.identifiers(identifierCollection)
.build();
@@ -526,8 +511,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
if(trafficChannel == null)
{
channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
channelGrantEvent.setEventDescription(channelGrantEvent.getEventDescription() + " - Ignored");
channelGrantEvent.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED + " - IGNORED");
return;
}
@@ -836,17 +820,7 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
if (event != null)
{
event.setEventDescription(event.getEventDescription() + " - Rejected");
if (channelEvent.getDescription() != null)
{
event.setDetails(channelEvent.getDescription() + " - " + event.getDetails());
}
else
{
event.setDetails(CHANNEL_START_REJECTED + " - " + event.getDetails());
}
event.setDetails(CHANNEL_START_REJECTED + " - " + event.getDetails());
broadcast(event);
}
});

View File

@@ -654,10 +654,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
private void processBroadcast(List<Identifier> identifiers, long timestamp, DecodeEventType request, String s) {
MutableIdentifierCollection requestCollection = getMutableIdentifierCollection(identifiers);
broadcast(P25DecodeEvent.builder(timestamp)
broadcast(P25DecodeEvent.builder(request, timestamp)
.channel(getCurrentChannel())
.eventType(request)
.eventDescription(request.toString())
.details(s)
.identifiers(requestCollection)
.build());
@@ -767,17 +765,16 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
/**
* Updates or creates a current call event.
*
* @param type of call that will be used as an event description
* @param decodeEventType of call that will be used as an event description
* @param details of the call (optional)
* @param timestamp of the message indicating a call or continuation
*/
private void updateCurrentCall(DecodeEventType type, String details, long timestamp)
private void updateCurrentCall(DecodeEventType decodeEventType, String details, long timestamp)
{
if(mCurrentCallEvent == null)
{
mCurrentCallEvent = P25DecodeEvent.builder(timestamp)
mCurrentCallEvent = P25DecodeEvent.builder(DecodeEventType.CALL, timestamp)
.channel(getCurrentChannel())
.eventDescription(type.toString())
.details(details)
.identifiers(getIdentifierCollection().copyOf())
.build();
@@ -791,9 +788,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
mCurrentCallEvent.end(timestamp);
broadcast(mCurrentCallEvent);
if(type == DecodeEventType.CALL_ENCRYPTED)
if(decodeEventType == DecodeEventType.CALL_ENCRYPTED)
{
mCurrentCallEvent.setEventDescription(type.toString());
mCurrentCallEvent.setDetails(details);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ENCRYPTED));
}
@@ -838,12 +834,9 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
*/
private void processPDU(P25Message message)
{
if(message.isValid() && message instanceof PDUMessage)
if(message.isValid() && message instanceof PDUMessage pdu)
{
PDUMessage pdu = (PDUMessage)message;
processBroadcast(pdu.getIdentifiers(), message.getTimestamp(), DecodeEventType.DATA_PACKET, pdu.toString());
}
broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA));
@@ -856,10 +849,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
*/
private void processUMBTC(P25Message message)
{
if(message.isValid() && message instanceof UMBTCTelephoneInterconnectRequestExplicitDialing)
if(message.isValid() && message instanceof UMBTCTelephoneInterconnectRequestExplicitDialing tired)
{
UMBTCTelephoneInterconnectRequestExplicitDialing tired = (UMBTCTelephoneInterconnectRequestExplicitDialing)message;
processBroadcast(tired.getIdentifiers(), tired.getTimestamp(), DecodeEventType.REQUEST, "TELEPHONE INTERCONNECT:" + tired.getTelephoneNumber());
}
@@ -910,11 +901,10 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE, message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE.toString())
.identifiers(ic)
.details(arsPacket.toString() + " " + ipv4.toString())
.details(arsPacket + " " + ipv4)
.build();
broadcast(packetEvent);
@@ -929,11 +919,10 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
ic.update(identifier);
}
DecodeEvent cellocatorEvent = P25DecodeEvent.builder(message.getTimestamp())
DecodeEvent cellocatorEvent = P25DecodeEvent.builder(DecodeEventType.CELLOCATOR, message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription("Cellocator")
.identifiers(ic)
.details(mcgpPacket.toString() + " " + ipv4.toString())
.details(mcgpPacket + " " + ipv4)
.build();
broadcast(cellocatorEvent);
@@ -942,10 +931,9 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
{
MutableIdentifierCollection ic = new MutableIdentifierCollection(packet.getIdentifiers());
DecodeEvent lrrpEvent = P25DecodeEvent.builder(message.getTimestamp())
DecodeEvent lrrpEvent = P25DecodeEvent.builder(DecodeEventType.LRRP, message.getTimestamp())
.channel(getCurrentChannel())
.details(lrrpPacket + " " + ipv4)
.eventDescription("LRRP")
.identifiers(ic)
.protocol(Protocol.LRRP)
.build();
@@ -956,9 +944,9 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
if(geoPosition != null)
{
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(message.getTimestamp())
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent
.plottableBuilder(DecodeEventType.GPS, message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.GPS.toString())
.identifiers(ic)
.protocol(Protocol.LRRP)
.location(geoPosition)
@@ -975,9 +963,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.UDP_PACKET, message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.UDP_PACKET.toString())
.identifiers(ic)
.details(ipv4.toString())
.build();
@@ -993,9 +980,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.ICMP_PACKET, message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.ICMP_PACKET.toString())
.identifiers(ic)
.details(ipv4.toString())
.build();
@@ -1010,9 +996,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.IP_PACKET, message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.IP_PACKET.toString())
.identifiers(ic)
.details(ipv4.toString())
.build();
@@ -1028,9 +1013,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
DecodeEvent packetEvent = P25DecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.UNKNOWN_PACKET.toString())
.identifiers(ic)
.details(packet.toString())
.build();
@@ -1376,9 +1360,8 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
private void processTSBKRoamingAddressRequest(TSBKMessage tsbk) {
//TODO: not sure if this should be used or not.
broadcast(P25DecodeEvent.builder(tsbk.getTimestamp())
broadcast(P25DecodeEvent.builder(DecodeEventType.REQUEST, tsbk.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.REQUEST.toString())
.details("ROAMING ADDRESS")
// TODO: This identifierCollection is different from all the others.
.identifiers(new IdentifierCollection(tsbk.getIdentifiers()))

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
@@ -24,32 +23,51 @@ import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
import java.util.function.Function;
import java.util.Collections;
import java.util.List;
public class HDUMessageFilter extends Filter<IMessage>
/**
* Filter for HDU header messages
*/
public class HeaderMessageFilter extends Filter<IMessage,String>
{
public HDUMessageFilter()
{
super("Header Data Unit");
}
private static final String HDU_KEY = "Header Data Unit (HDU)";
private KeyExtractor mKeyExtractor = new KeyExtractor();
@Override
public boolean passes(IMessage message)
/**
* Constructor
*/
public HeaderMessageFilter()
{
return mEnabled && canProcess(message);
super("Header Messages");
add(new FilterElement<>(HDU_KEY));
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof HDUMessage;
return message instanceof HDUMessage && super.canProcess(message);
}
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, String> getKeyExtractor()
{
return Collections.EMPTY_LIST;
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
@Override
public String apply(IMessage message)
{
if(message instanceof HDUMessage)
{
return HDU_KEY;
}
return null;
}
}
}

View File

@@ -1,55 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* 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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage;
import java.util.Collections;
import java.util.List;
public class IPPacketMessageFilter extends Filter<IMessage>
{
public IPPacketMessageFilter()
{
super("Packet Message");
}
@Override
public boolean passes(IMessage message)
{
return mEnabled && canProcess(message);
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof PacketMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
@@ -23,33 +22,52 @@ package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.ldu.LDUMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
import java.util.function.Function;
import java.util.Collections;
import java.util.List;
public class LDUMessageFilter extends Filter<IMessage>
/**
* Filter for unknown messages, meaning messages not handled by any other filter.
*/
public class P25OtherMessageFilter extends Filter<IMessage,String>
{
public LDUMessageFilter()
private static final String OTHER_KEY = "Other/Unknown Message";
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public P25OtherMessageFilter()
{
super("Link Data Unit");
super("Other P25 messages");
add(new FilterElement<>(OTHER_KEY));
}
@Override
public boolean passes(IMessage message)
public Function<IMessage, String> getKeyExtractor()
{
return mEnabled && canProcess(message);
return mKeyExtractor;
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof LDUMessage;
return message instanceof P25Message && super.canProcess(message);
}
@Override
public List<FilterElement<?>> getFilterElements()
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
return Collections.EMPTY_LIST;
@Override
public String apply(IMessage message)
{
if(message instanceof P25Message)
{
return OTHER_KEY;
}
return null;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,35 +14,42 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.filter.SyncLossMessageFilter;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.SyncLossMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
public class P25MessageFilterSet extends FilterSet<IMessage>
/**
* Filter set for P25 messages
*/
public class P25P1MessageFilterSet extends FilterSet<IMessage>
{
public P25MessageFilterSet()
public P25P1MessageFilterSet()
{
super("P25 Message Filter");
addFilter(new AMBTCMessageFilter());
addFilter(new HDUMessageFilter());
addFilter(new IPPacketMessageFilter());
addFilter(new LDUMessageFilter());
addFilter(new HeaderMessageFilter());
addFilter(new PacketMessageFilter());
addFilter(new PDUMessageFilter());
addFilter(new SNDCPMessageFilter());
addFilter(new SyncLossMessageFilter());
addFilter(new TDUMessageFilter());
addFilter(new TDULCMessageFilter());
addFilter(new TSBKMessageFilterSet());
addFilter(new UMBTCMessageFilter());
addFilter(new TerminatorMessageFilterSet());
addFilter(new TrunkingOpcodeMessageFilterSet());
addFilter(new VoiceMessageFilter());
addFilter(new P25OtherMessageFilter());
}
/**
* Override default to descope handling to P25 or sync-loss messages.
* @param message to test
* @return true if the message can be processed
*/
@Override
public boolean canProcess(IMessage message)
{

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
@@ -24,49 +23,52 @@ import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage;
import java.util.function.Function;
import java.util.*;
public class PDUMessageFilter extends Filter<IMessage>
/**
* Filter for PDU packet data unit messages
*/
public class PDUMessageFilter extends Filter<IMessage,String>
{
private Map<Opcode, FilterElement<Opcode>> mOpcodeFilterElements = new EnumMap<>(Opcode.class);
private static final String PDU_KEY = "Packet Data Unit (PDU)";
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public PDUMessageFilter()
{
super("PDU - Packet Data Unit");
for(Opcode opcode : Opcode.values())
{
mOpcodeFilterElements.put(opcode, new FilterElement<Opcode>(opcode));
}
super("Packet Data Unit messages");
add(new FilterElement<>(PDU_KEY));
}
@Override
public boolean passes(IMessage message)
public Function<IMessage, String> getKeyExtractor()
{
if(mEnabled && message instanceof PDUMessage)
{
PDUMessage pdu = (PDUMessage) message;
Opcode opcode = pdu.getOpcode();
return mOpcodeFilterElements.get(opcode).isEnabled();
}
return false;
return mKeyExtractor;
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof PDUMessage;
return message instanceof TDUMessage && super.canProcess(message);
}
@Override
public List<FilterElement<?>> getFilterElements()
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
return new ArrayList<FilterElement<?>>(mOpcodeFilterElements.values());
@Override
public String apply(IMessage message)
{
if(message instanceof PDUMessage)
{
return PDU_KEY;
}
return null;
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.ip.IPacket;
import io.github.dsheirer.module.decode.ip.cellocator.MCGPPacket;
import io.github.dsheirer.module.decode.ip.icmp.ICMPPacket;
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketMessage;
import java.util.function.Function;
/**
* Filter for packet messages
*/
public class PacketMessageFilter extends Filter<IMessage,String>
{
private static final String KEY_CELLOCATOR = "Cellocator Packet";
private static final String KEY_IPV4_OTHER = "IPV4 Other Packet";
private static final String KEY_IPV4_UDP_ICMP = "IPV4 UDP/IP ICMP Packet";
private static final String KEY_IPV4_UDP_OTHER = "IPV4 UDP/IP Other Packet";
private static final String KEY_MOTOROLA_ARS = "Motorola Registration (ARS) Packet";
private static final String KEY_MOTOROLA_LRRP = "Motorola Location Report (LRRP) Packet";
private static final String KEY_MOTOROLA_XCMP = "Motorola Extensible (XCMP) Packet";
private static final String KEY_SNDCP = "SNDCP Data Packet";
private static final String KEY_UNKNOWN = "Unknown Packet";
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public PacketMessageFilter()
{
super("Packet Messages");
add(new FilterElement<>(KEY_CELLOCATOR));
add(new FilterElement<>(KEY_IPV4_OTHER));
add(new FilterElement<>(KEY_IPV4_UDP_ICMP));
add(new FilterElement<>(KEY_IPV4_UDP_OTHER));
add(new FilterElement<>(KEY_MOTOROLA_ARS));
add(new FilterElement<>(KEY_MOTOROLA_LRRP));
add(new FilterElement<>(KEY_MOTOROLA_XCMP));
add(new FilterElement<>(KEY_SNDCP));
add(new FilterElement<>(KEY_UNKNOWN));
}
@Override
public Function<IMessage, String> getKeyExtractor()
{
return mKeyExtractor;
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof PacketMessage && super.canProcess(message);
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
@Override
public String apply(IMessage message)
{
if(message instanceof PacketMessage packetMessage)
{
IPacket packet = packetMessage.getPacket();
if(packet instanceof MCGPPacket)
{
return KEY_CELLOCATOR;
}
else if(packet instanceof ICMPPacket)
{
return KEY_IPV4_UDP_ICMP;
}
else if(packet instanceof UDPPacket)
{
return KEY_IPV4_UDP_OTHER;
}
else if(packet instanceof IPV4Packet)
{
return KEY_IPV4_OTHER;
}
else if(packet instanceof SNDCPPacketMessage)
{
return KEY_SNDCP;
}
else if(packet instanceof ARSPacket)
{
return KEY_MOTOROLA_ARS;
}
else if(packet instanceof LRRPPacket)
{
return KEY_MOTOROLA_LRRP;
}
else if(packet instanceof XCMPPacket)
{
return KEY_MOTOROLA_XCMP;
}
else
{
return KEY_UNKNOWN;
}
}
return null;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
@@ -24,32 +23,55 @@ import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPMessage;
import io.github.dsheirer.module.decode.p25.reference.PDUType;
import java.util.function.Function;
import java.util.Collections;
import java.util.List;
public class SNDCPMessageFilter extends Filter<IMessage>
/**
* Filter for SNDCP messages
*/
public class SNDCPMessageFilter extends Filter<IMessage, PDUType>
{
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public SNDCPMessageFilter()
{
super("Sub-Network Dependent Convergence Protocol");
}
super("Sub-Network Dependent Convergence Protocol (SNDCP) Messages");
@Override
public boolean passes(IMessage message)
{
return mEnabled && canProcess(message);
for(PDUType pduType: PDUType.values())
{
add(new FilterElement<>(pduType));
}
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof SNDCPMessage;
return message instanceof SNDCPMessage && super.canProcess(message);
}
@Override
public List<FilterElement<?>> getFilterElements()
public Function<IMessage, PDUType> getKeyExtractor()
{
return Collections.EMPTY_LIST;
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,PDUType>
{
@Override
public PDUType apply(IMessage message)
{
if(message instanceof SNDCPMessage sndcp)
{
return sndcp.getPDUType();
}
return null;
}
}
}

View File

@@ -1,55 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* 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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
import java.util.Collections;
import java.util.List;
public class TDULCMessageFilter extends Filter<IMessage>
{
public TDULCMessageFilter()
{
super("TDU Terminator Data Unit with Link Control");
}
@Override
public boolean passes(IMessage message)
{
return mEnabled && canProcess(message);
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof TDULinkControlMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return Collections.EMPTY_LIST;
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
@@ -24,32 +23,51 @@ import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage;
import java.util.function.Function;
import java.util.Collections;
import java.util.List;
public class TDUMessageFilter extends Filter<IMessage>
/**
* Filter for TDU terminator (no link control) messages
*/
public class TDUMessageFilter extends Filter<IMessage,String>
{
private static final String TDU_KEY = "Terminator Data Unit (TDU)";
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public TDUMessageFilter()
{
super("TDU Terminator Data Unit");
super("Terminator/No Link Control messages");
add(new FilterElement<>(TDU_KEY));
}
@Override
public boolean passes(IMessage message)
public Function<IMessage, String> getKeyExtractor()
{
return mEnabled && canProcess(message);
return mKeyExtractor;
}
@Override
public boolean canProcess(IMessage message)
{
return message instanceof TDUMessage;
return message instanceof TDUMessage && super.canProcess(message);
}
@Override
public List<FilterElement<?>> getFilterElements()
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
return Collections.EMPTY_LIST;
@Override
public String apply(IMessage message)
{
if(message instanceof TDUMessage)
{
return TDU_KEY;
}
return null;
}
}
}

View File

@@ -1,113 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* 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 <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
import io.github.dsheirer.module.decode.p25.reference.Vendor;
import java.util.*;
public class TSBKMessageFilterSet extends FilterSet<IMessage>
{
public TSBKMessageFilterSet()
{
super("TSBK Trunking Signalling Block");
addFilter(new StandardOpcodeFilter());
}
// @Override
// public boolean passes( Message message )
// {
// if( mEnabled && canProcess( message ) )
// {
// TSBKMessage tsbk = (TSBKMessage) message;
//
// switch( tsbk.getVendor() )
// {
// case STANDARD:
// return
// }
// Opcode opcode = tsbk.getOpcode();
//
// return mFilterElements.get( opcode ).isEnabled();
// }
//
// return false;
// }
@Override
public boolean canProcess(IMessage message)
{
return message instanceof TSBKMessage;
}
public class StandardOpcodeFilter extends Filter<IMessage>
{
private Map<Opcode, FilterElement<Opcode>> mStandardElements = new EnumMap<>(Opcode.class);
public StandardOpcodeFilter()
{
super("Standard Opcode Filter");
for(Opcode opcode : Opcode.values())
{
if(opcode != Opcode.OSP_UNKNOWN)
{
mStandardElements.put(opcode, new FilterElement<Opcode>(opcode));
}
}
}
@Override
public boolean passes(IMessage message)
{
if(mEnabled && canProcess(message))
{
Opcode opcode = ((TSBKMessage) message).getOpcode();
if(mStandardElements.containsKey(opcode))
{
return mStandardElements.get(opcode).isEnabled();
}
}
return false;
}
@Override
public boolean canProcess(IMessage message)
{
return ((TSBKMessage) message).getVendor() == Vendor.STANDARD;
}
@Override
public List<FilterElement<?>> getFilterElements()
{
return new ArrayList<FilterElement<?>>(mStandardElements.values());
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
import java.util.Collection;
import java.util.function.Function;
/**
* Filter for TDU terminator and TDULC terminator with link control messages
*/
public class TerminatorMessageFilter extends Filter<IMessage, LinkControlOpcode>
{
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructs an instance
*
* @param name of this filter
*/
public TerminatorMessageFilter(String name, Collection<LinkControlOpcode> opcodes)
{
super(name);
for(LinkControlOpcode opcode: opcodes)
{
add(new FilterElement<>(opcode));
}
}
@Override
public Function<IMessage, LinkControlOpcode> getKeyExtractor()
{
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,LinkControlOpcode>
{
@Override
public LinkControlOpcode apply(IMessage message)
{
if(message instanceof TDULinkControlMessage tdulc)
{
return tdulc.getLinkControlWord().getOpcode();
}
return null;
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlOpcode;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDUMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Filter set for terminator/link control messages
*/
public class TerminatorMessageFilterSet extends FilterSet<IMessage>
{
/**
* Constructor
*/
public TerminatorMessageFilterSet()
{
super("Terminator Messages");
addFilter(new TDUMessageFilter());
addFilter(new TerminatorMessageFilter("Command/Status", LinkControlOpcode.COMMAND_STATUS_OPCODES));
addFilter(new TerminatorMessageFilter("Network/Channel", LinkControlOpcode.NETWORK_OPCODES));
addFilter(new TerminatorMessageFilter("Voice", LinkControlOpcode.VOICE_OPCODES));
addFilter(new TerminatorMessageFilter("Motorola", LinkControlOpcode.MOTOROLA_OPCODES));
List<LinkControlOpcode> others = new ArrayList<>();
for(LinkControlOpcode opcode: LinkControlOpcode.values())
{
if(!opcode.isGrouped())
{
others.add(opcode);
}
}
addFilter(new TerminatorMessageFilter("Other/Reserved", others));
}
@Override
public boolean canProcess(IMessage message)
{
return (message instanceof TDUMessage || message instanceof TDULinkControlMessage) && super.canProcess(message);
}
}

View File

@@ -0,0 +1,91 @@
/*
* *****************************************************************************
* 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.
*
* 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.AMBTCMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.UMBTCMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.Opcode;
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
import java.util.Collection;
import java.util.function.Function;
/**
* Message filter for P25 TSBK, AMBTC and UMBTC trunking control messages.
*/
public class TrunkingOpcodeMessageFilter extends Filter<IMessage, Opcode>
{
private KeyExtractor mKeyExtractor = new KeyExtractor();
private Collection<Opcode> mOpcodes;
/**
* Constructs an instance
*
* @param name of this filter
*/
public TrunkingOpcodeMessageFilter(String name, Collection<Opcode> opcodes)
{
super(name);
mOpcodes = opcodes;
for(Opcode opcode: opcodes)
{
add(new FilterElement<>(opcode));
}
}
/**
* Key extractor
* @return opcode extractor
*/
@Override
public Function<IMessage, Opcode> getKeyExtractor()
{
return mKeyExtractor;
}
/**
* Key extractor for Opcode values from TSBK, AMBTC and UMBTC messages.
*/
private class KeyExtractor implements Function<IMessage,Opcode>
{
@Override
public Opcode apply(IMessage message)
{
if(message instanceof TSBKMessage tsbk)
{
return tsbk.getOpcode();
}
else if(message instanceof AMBTCMessage ambtc)
{
return ambtc.getHeader().getOpcode();
}
else if(message instanceof UMBTCMessage umbtc)
{
return umbtc.getOpcode();
}
return null;
}
}
}

Some files were not shown because too many files have changed in this diff Show More