#2529 NXDN adds aliasing support for Type-D identifiers and fixes issue in certain NXDN messaing where the TO identifiers are incorrectly tagged. (#2547)

Co-authored-by: denny <dennis.sheirer@gmail.com>
This commit is contained in:
Denny Sheirer
2026-11-01 05:21:28 -05:00
committed by GitHub
parent c75baa48f4
commit 197cec04f3
19 changed files with 1147 additions and 11 deletions

View File

@@ -33,16 +33,22 @@ import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeD;
import io.github.dsheirer.alias.id.INXDNTypeDRange;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadio;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadioRange;
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.radio.RadioFormatter;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.StreamAsTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
@@ -70,6 +76,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
@@ -770,6 +777,12 @@ public class AliasItemEditor extends Editor<Alias>
nxdnMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.NXDN));
nxdnMenu.getItems().add(new AddRadioIdItem(Protocol.NXDN));
nxdnMenu.getItems().add(new AddRadioIdRangeItem(Protocol.NXDN));
nxdnMenu.getItems().add(new SeparatorMenuItem());
nxdnMenu.getItems().add(new AddNXDNTypeDItem("Type-D Talkgroup", NXDNTypeDTalkgroup::new));
nxdnMenu.getItems().add(new AddNXDNTypeDItem("Type-D Talkgroup Range", NXDNTypeDTalkgroupRange::new));
nxdnMenu.getItems().add(new AddNXDNTypeDItem("Type-D Radio ID", NXDNTypeDRadio::new));
nxdnMenu.getItems().add(new AddNXDNTypeDItem("Type-D Radio ID Range", NXDNTypeDRadioRange::new));
nxdnMenu.getItems().add(new SeparatorMenuItem());
nxdnMenu.getItems().add(new AddTonesItem("AMBE Audio Tones"));
Menu passportMenu = new ProtocolMenu(Protocol.PASSPORT);
@@ -1456,6 +1469,24 @@ public class AliasItemEditor extends Editor<Alias>
}
}
/**
* Menu Item for adding a new NXDN Type-D alias identifier
*/
public class AddNXDNTypeDItem extends MenuItem
{
public AddNXDNTypeDItem(String label, Supplier<AliasID> supplier)
{
super(label);
setOnAction(event -> {
AliasID aliasID = supplier.get();
getIdentifiersList().getItems().add(aliasID);
getIdentifiersList().getSelectionModel().select(aliasID);
getIdentifiersList().scrollTo(aliasID);
modifiedProperty().set(true);
});
}
}
/**
* Menu Item for adding a new protocol-specific Talkgroup alias identifier
*/
@@ -1617,7 +1648,11 @@ public class AliasItemEditor extends Editor<Alias>
if(item != null)
{
if(item instanceof P25FullyQualifiedTalkgroup fqt)
if(item instanceof INXDNTypeD || item instanceof INXDNTypeDRange)
{
setText(item.overlapProperty().get() ? item + " - Error: Overlap" : item.toString());
}
else if(item instanceof P25FullyQualifiedTalkgroup fqt)
{
StringBuilder sb = new StringBuilder();
sb.append("APCO-25 Fully Qualified Talkgroup:").append(fqt.getWacn());

View File

@@ -20,6 +20,10 @@
package io.github.dsheirer.gui.playlist.alias.identifier;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadio;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadioRange;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroupRange;
import io.github.dsheirer.preference.UserPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,6 +45,14 @@ public class IdentifierEditorFactory
return new EsnEditor();
case LOJACK:
return new LojackEditor();
case NXDN_TYPE_D_RADIO_ID:
return new NXDNTypeDEditor<NXDNTypeDRadio>("Radio");
case NXDN_TYPE_D_RADIO_ID_RANGE:
return new NXDNTypeDRangeEditor<NXDNTypeDRadioRange>("Radio Range");
case NXDN_TYPE_D_TALKGROUP:
return new NXDNTypeDEditor<NXDNTypeDTalkgroup>("Talkgroup");
case NXDN_TYPE_D_TALKGROUP_RANGE:
return new NXDNTypeDRangeEditor<NXDNTypeDTalkgroupRange>("Talkgroup Range");
case P25_FULLY_QUALIFIED_RADIO_ID:
return new P25FullyQualifiedRadioIdEditor(userPreferences);
case P25_FULLY_QUALIFIED_TALKGROUP:

View File

@@ -0,0 +1,122 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.gui.playlist.alias.identifier;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.INXDNTypeD;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import javafx.beans.value.ChangeListener;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
/**
* Editor for NXDN Type-D radio and talkgroup alias identifiers
* @param <T> alias identifier to edit
*/
public class NXDNTypeDEditor<T extends AliasID & INXDNTypeD> extends IdentifierEditor<T>
{
private final TextField mHomeRepeaterField = new TextField();
private final TextField mIdentifierField = new TextField();
private final TextFormatter<Integer> mHomeRepeaterFormatter =
new IntegerFormatter(0, NXDNTalkgroupFormatter.TYPE_D_MAX_HOME_REPEATER);
private final TextFormatter<Integer> mIdentifierFormatter =
new IntegerFormatter(0, NXDNTalkgroupFormatter.TYPE_D_MAX_IDENTIFIER);
private final ChangeListener<Integer> mHomeRepeaterListener = (observable, oldValue, newValue) -> {
if(getItem() != null)
{
getItem().setHomeRepeater(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
};
private final ChangeListener<Integer> mIdentifierListener = (observable, oldValue, newValue) -> {
if(getItem() != null)
{
getItem().setIdentifier(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
};
/**
* Constructs an instance
* @param identifierLabel for the identifier field: Radio or Talkgroup
*/
public NXDNTypeDEditor(String identifierLabel)
{
mHomeRepeaterField.setTextFormatter(mHomeRepeaterFormatter);
mHomeRepeaterField.setTooltip(new Tooltip("Home repeater: 0 - " + NXDNTalkgroupFormatter.TYPE_D_MAX_HOME_REPEATER));
mIdentifierField.setTextFormatter(mIdentifierFormatter);
mIdentifierField.setTooltip(new Tooltip(identifierLabel + ": 0 - " + NXDNTalkgroupFormatter.TYPE_D_MAX_IDENTIFIER));
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(3);
gridPane.add(new Label("NXDN Type-D"), 0, 0);
Label homeRepeaterLabel = new Label("Home Repeater");
GridPane.setHalignment(homeRepeaterLabel, HPos.RIGHT);
gridPane.add(homeRepeaterLabel, 1, 0);
gridPane.add(mHomeRepeaterField, 2, 0);
Label identifierFieldLabel = new Label(identifierLabel);
GridPane.setHalignment(identifierFieldLabel, HPos.RIGHT);
gridPane.add(identifierFieldLabel, 3, 0);
gridPane.add(mIdentifierField, 4, 0);
getChildren().add(gridPane);
}
@Override
public void setItem(T item)
{
//Clear the item and listeners so that loading the field values doesn't change the item
mHomeRepeaterFormatter.valueProperty().removeListener(mHomeRepeaterListener);
mIdentifierFormatter.valueProperty().removeListener(mIdentifierListener);
super.setItem(item);
mHomeRepeaterField.setDisable(item == null);
mIdentifierField.setDisable(item == null);
mHomeRepeaterFormatter.setValue(item != null ? item.getHomeRepeater() : null);
mIdentifierFormatter.setValue(item != null ? item.getIdentifier() : null);
mHomeRepeaterFormatter.valueProperty().addListener(mHomeRepeaterListener);
mIdentifierFormatter.valueProperty().addListener(mIdentifierListener);
modifiedProperty().set(false);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
}

View File

@@ -0,0 +1,141 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2026 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.gui.playlist.alias.identifier;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.INXDNTypeDRange;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import javafx.beans.value.ChangeListener;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
/**
* Editor for NXDN Type-D radio and talkgroup range alias identifiers
* @param <T> alias identifier to edit
*/
public class NXDNTypeDRangeEditor<T extends AliasID & INXDNTypeDRange> extends IdentifierEditor<T>
{
private final TextField mHomeRepeaterField = new TextField();
private final TextField mMinField = new TextField();
private final TextField mMaxField = new TextField();
private final TextFormatter<Integer> mHomeRepeaterFormatter =
new IntegerFormatter(0, NXDNTalkgroupFormatter.TYPE_D_MAX_HOME_REPEATER);
private final TextFormatter<Integer> mMinFormatter =
new IntegerFormatter(0, NXDNTalkgroupFormatter.TYPE_D_MAX_IDENTIFIER);
private final TextFormatter<Integer> mMaxFormatter =
new IntegerFormatter(0, NXDNTalkgroupFormatter.TYPE_D_MAX_IDENTIFIER);
private final ChangeListener<Integer> mHomeRepeaterListener = (observable, oldValue, newValue) -> {
if(getItem() != null)
{
getItem().setHomeRepeater(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
};
private final ChangeListener<Integer> mMinListener = (observable, oldValue, newValue) -> {
if(getItem() != null)
{
getItem().setMinIdentifier(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
};
private final ChangeListener<Integer> mMaxListener = (observable, oldValue, newValue) -> {
if(getItem() != null)
{
getItem().setMaxIdentifier(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
};
/**
* Constructs an instance
* @param rangeLabel for the range fields: Radio Range or Talkgroup Range
*/
public NXDNTypeDRangeEditor(String rangeLabel)
{
String identifierTooltip = rangeLabel + ": 0 - " + NXDNTalkgroupFormatter.TYPE_D_MAX_IDENTIFIER;
mHomeRepeaterField.setTextFormatter(mHomeRepeaterFormatter);
mHomeRepeaterField.setTooltip(new Tooltip("Home repeater: 0 - " + NXDNTalkgroupFormatter.TYPE_D_MAX_HOME_REPEATER));
mMinField.setTextFormatter(mMinFormatter);
mMinField.setTooltip(new Tooltip(identifierTooltip));
mMaxField.setTextFormatter(mMaxFormatter);
mMaxField.setTooltip(new Tooltip(identifierTooltip));
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(3);
gridPane.add(new Label("NXDN Type-D"), 0, 0);
Label homeRepeaterLabel = new Label("Home Repeater");
GridPane.setHalignment(homeRepeaterLabel, HPos.RIGHT);
gridPane.add(homeRepeaterLabel, 1, 0);
gridPane.add(mHomeRepeaterField, 2, 0);
Label rangeFieldLabel = new Label(rangeLabel);
GridPane.setHalignment(rangeFieldLabel, HPos.RIGHT);
gridPane.add(rangeFieldLabel, 3, 0);
gridPane.add(mMinField, 4, 0);
gridPane.add(new Label("-"), 5, 0);
gridPane.add(mMaxField, 6, 0);
getChildren().add(gridPane);
}
@Override
public void setItem(T item)
{
//Clear the item and listeners so that loading the field values doesn't change the item
mHomeRepeaterFormatter.valueProperty().removeListener(mHomeRepeaterListener);
mMinFormatter.valueProperty().removeListener(mMinListener);
mMaxFormatter.valueProperty().removeListener(mMaxListener);
super.setItem(item);
mHomeRepeaterField.setDisable(item == null);
mMinField.setDisable(item == null);
mMaxField.setDisable(item == null);
mHomeRepeaterFormatter.setValue(item != null ? item.getHomeRepeater() : null);
mMinFormatter.setValue(item != null ? item.getMinIdentifier() : null);
mMaxFormatter.setValue(item != null ? item.getMaxIdentifier() : null);
mHomeRepeaterFormatter.valueProperty().addListener(mHomeRepeaterListener);
mMinFormatter.valueProperty().addListener(mMinListener);
mMaxFormatter.valueProperty().addListener(mMaxListener);
modifiedProperty().set(false);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
}

View File

@@ -293,9 +293,9 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
new HexFormatter(0,0xFFFFFF), "Format: 0 - FFFFFF"));
details.add(new RadioIdDetail(Protocol.NXDN, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
new IntegerFormatter(0,0xFFFF),"Format: 0 - 65,535"));
new IntegerFormatter(0,0xFFFF),"Format: 1 - 65,535"));
details.add(new RadioIdDetail(Protocol.NXDN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
new IntegerFormatter(0,0xFFFF),"Format: 0 - FFFF"));
new HexFormatter(0,0xFFFF),"Format: 1 - FFFF"));
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
new IntegerFormatter(0,0x7FFFFF), "Format: 0 - 8388607"));
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),

View File

@@ -292,8 +292,8 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
List<TalkgroupDetail> details = new ArrayList<>();
details.add(new TalkgroupDetail(Protocol.AM, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
new IntegerFormatter(0,65535), "Format 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.AM, IntegerFormat.HEXADECIMAL, new IntegerFormatter(0,65535),
new IntegerFormatter(0,65535), "Format 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.AM, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
new HexFormatter(0,65535), "Format 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
new IntegerFormatter(0,65535), "Format: 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
@@ -316,12 +316,12 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
"Format: PPP-IIII = Prefix (0-127), Ident (1-8191)"));
details.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
new IntegerFormatter(0,65535), "Format 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.HEXADECIMAL, new IntegerFormatter(0,65535),
new IntegerFormatter(0,65535), "Format 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
new HexFormatter(0,65535), "Format 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.NXDN, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFF),
new IntegerFormatter(1,0xFFFF),"Format: 1 - 65,535"));
details.add(new TalkgroupDetail(Protocol.NXDN, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFF),
new IntegerFormatter(1,0xFFFF),"Format: 1 - FFFF"));
new HexFormatter(1,0xFFFF),"Format: 1 - FFFF"));
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),