#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

@@ -30,12 +30,16 @@ import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
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.RadioRange;
import io.github.dsheirer.alias.id.record.Record;
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.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
@@ -79,6 +83,31 @@ public class AliasFactory
Min copyMin = new Min();
copyMin.setMin(originalMin.getMin());
return copyMin;
case NXDN_TYPE_D_RADIO_ID:
NXDNTypeDRadio originalTypeDRadio = (NXDNTypeDRadio)id;
NXDNTypeDRadio copyTypeDRadio = new NXDNTypeDRadio(originalTypeDRadio.getHomeRepeater(),
originalTypeDRadio.getIdentifier());
copyTypeDRadio.setOverlap(originalTypeDRadio.overlapProperty().get());
return copyTypeDRadio;
case NXDN_TYPE_D_RADIO_ID_RANGE:
NXDNTypeDRadioRange originalTypeDRadioRange = (NXDNTypeDRadioRange)id;
NXDNTypeDRadioRange copyTypeDRadioRange = new NXDNTypeDRadioRange(originalTypeDRadioRange.getHomeRepeater(),
originalTypeDRadioRange.getMinIdentifier(), originalTypeDRadioRange.getMaxIdentifier());
copyTypeDRadioRange.setOverlap(originalTypeDRadioRange.overlapProperty().get());
return copyTypeDRadioRange;
case NXDN_TYPE_D_TALKGROUP:
NXDNTypeDTalkgroup originalTypeDTalkgroup = (NXDNTypeDTalkgroup)id;
NXDNTypeDTalkgroup copyTypeDTalkgroup = new NXDNTypeDTalkgroup(originalTypeDTalkgroup.getHomeRepeater(),
originalTypeDTalkgroup.getIdentifier());
copyTypeDTalkgroup.setOverlap(originalTypeDTalkgroup.overlapProperty().get());
return copyTypeDTalkgroup;
case NXDN_TYPE_D_TALKGROUP_RANGE:
NXDNTypeDTalkgroupRange originalTypeDTalkgroupRange = (NXDNTypeDTalkgroupRange)id;
NXDNTypeDTalkgroupRange copyTypeDTalkgroupRange = new NXDNTypeDTalkgroupRange(
originalTypeDTalkgroupRange.getHomeRepeater(), originalTypeDTalkgroupRange.getMinIdentifier(),
originalTypeDTalkgroupRange.getMaxIdentifier());
copyTypeDTalkgroupRange.setOverlap(originalTypeDTalkgroupRange.overlapProperty().get());
return copyTypeDTalkgroupRange;
case P25_FULLY_QUALIFIED_RADIO_ID:
P25FullyQualifiedRadio originalP25 = (P25FullyQualifiedRadio) id;
P25FullyQualifiedRadio copyP25 = new P25FullyQualifiedRadio(originalP25.getWacn(),

View File

@@ -134,6 +134,7 @@ public class AliasList
switch(id.getType())
{
case TALKGROUP:
case NXDN_TYPE_D_TALKGROUP:
Talkgroup talkgroup = (Talkgroup)id;
TalkgroupAliasList talkgroupAliasList = mTalkgroupProtocolMap.get(talkgroup.getProtocol());
@@ -147,6 +148,7 @@ public class AliasList
talkgroupAliasList.add(talkgroup, alias);
break;
case TALKGROUP_RANGE:
case NXDN_TYPE_D_TALKGROUP_RANGE:
TalkgroupRange talkgroupRange = (TalkgroupRange)id;
TalkgroupAliasList talkgroupRangeAliasList = mTalkgroupProtocolMap.get(talkgroupRange.getProtocol());
@@ -186,6 +188,7 @@ public class AliasList
p25TalkgroupAliasList.add(qualifiedTalkgroup, alias);
break;
case RADIO_ID:
case NXDN_TYPE_D_RADIO_ID:
Radio radio = (Radio)id;
RadioAliasList radioAliasList = mRadioProtocolMap.get(radio.getProtocol());
@@ -199,6 +202,7 @@ public class AliasList
radioAliasList.add(radio, alias);
break;
case RADIO_ID_RANGE:
case NXDN_TYPE_D_RADIO_ID_RANGE:
RadioRange radioRange = (RadioRange)id;
RadioAliasList radioRangeAliasList = mRadioProtocolMap.get(radioRange.getProtocol());

View File

@@ -35,12 +35,16 @@ import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
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.RadioRange;
import io.github.dsheirer.alias.id.record.Record;
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;
@@ -64,6 +68,10 @@ import javafx.util.Callback;
@JsonSubTypes.Type(value = Min.class, name = "min"),
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
@JsonSubTypes.Type(value = NXDNTypeDRadio.class, name = "nxdnTypeDRadio"),
@JsonSubTypes.Type(value = NXDNTypeDRadioRange.class, name = "nxdnTypeDRadioRange"),
@JsonSubTypes.Type(value = NXDNTypeDTalkgroup.class, name = "nxdnTypeDTalkgroup"),
@JsonSubTypes.Type(value = NXDNTypeDTalkgroupRange.class, name = "nxdnTypeDTalkgroupRange"),
@JsonSubTypes.Type(value = P25FullyQualifiedRadio.class, name = "p25FullyQualifiedRadio"),
@JsonSubTypes.Type(value = P25FullyQualifiedTalkgroup.class, name = "p25FullyQualifiedTalkgroup"),
@JsonSubTypes.Type(value = Priority.class, name = "priority"),

View File

@@ -29,6 +29,10 @@ public enum AliasIDType
LOJACK("LoJack"),
LTR_NET_UID("LTR-Net UID"),
MIN("Passport MIN"),
NXDN_TYPE_D_RADIO_ID("NXDN Type-D Radio ID"),
NXDN_TYPE_D_RADIO_ID_RANGE("NXDN Type-D Radio ID Range"),
NXDN_TYPE_D_TALKGROUP("NXDN Type-D Talkgroup"),
NXDN_TYPE_D_TALKGROUP_RANGE("NXDN Type-D Talkgroup Range"),
P25_FULLY_QUALIFIED_RADIO_ID("P25 Fully Qualified Radio ID"),
P25_FULLY_QUALIFIED_TALKGROUP("P25 Fully Qualified Talkgroup"),
PRIORITY("Audio Priority"),
@@ -58,6 +62,7 @@ public enum AliasIDType
//Values used by the View-By alias editor
public static EnumSet<AliasIDType> VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE,
NXDN_TYPE_D_RADIO_ID, NXDN_TYPE_D_RADIO_ID_RANGE, NXDN_TYPE_D_TALKGROUP, NXDN_TYPE_D_TALKGROUP_RANGE,
P25_FULLY_QUALIFIED_RADIO_ID, P25_FULLY_QUALIFIED_TALKGROUP, UNIT_STATUS, STATUS, TONES);
public String toString()

View File

@@ -0,0 +1,46 @@
/*
* *****************************************************************************
* 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.alias.id;
/**
* NXDN Type-D alias identifier composed of a home repeater and a radio or talkgroup identifier value.
*/
public interface INXDNTypeD
{
/**
* Home repeater for the identifier
*/
int getHomeRepeater();
/**
* Sets the home repeater for the identifier
*/
void setHomeRepeater(int homeRepeater);
/**
* Radio or talkgroup value, without the home repeater.
*/
int getIdentifier();
/**
* Sets the radio or talkgroup value, without the home repeater.
*/
void setIdentifier(int identifier);
}

View File

@@ -0,0 +1,56 @@
/*
* *****************************************************************************
* 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.alias.id;
/**
* NXDN Type-D alias identifier range of radio or talkgroup values that share a common home repeater.
*/
public interface INXDNTypeDRange
{
/**
* Home repeater for the range
*/
int getHomeRepeater();
/**
* Sets the home repeater for the range
*/
void setHomeRepeater(int homeRepeater);
/**
* Minimum radio or talkgroup value, without the home repeater.
*/
int getMinIdentifier();
/**
* Sets the minimum radio or talkgroup value, without the home repeater.
*/
void setMinIdentifier(int identifier);
/**
* Maximum radio or talkgroup value, without the home repeater.
*/
int getMaxIdentifier();
/**
* Sets the maximum radio or talkgroup value, without the home repeater.
*/
void setMaxIdentifier(int identifier);
}

View File

@@ -0,0 +1,96 @@
/*
* *****************************************************************************
* 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.alias.id.radio;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeD;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D radio. The value is the over-the-air 16-bit identifier that combines the home repeater and radio so that
* it matches the value of an NXDN Type-D radio identifier.
*/
public class NXDNTypeDRadio extends Radio implements INXDNTypeD
{
public NXDNTypeDRadio()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the radio
* @param radio value, without the home repeater
*/
public NXDNTypeDRadio(int homeRepeater, int radio)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, radio));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_RADIO_ID;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getValue());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, getIdentifier()));
}
@JsonIgnore
@Override
public int getIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getValue());
}
@Override
public void setIdentifier(int identifier)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Radio ID:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,124 @@
/*
* *****************************************************************************
* 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.alias.id.radio;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeDRange;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D radio range for a single home repeater. The min and max values are the over-the-air 16-bit identifiers
* that combine the home repeater and radio.
*/
public class NXDNTypeDRadioRange extends RadioRange implements INXDNTypeDRange
{
public NXDNTypeDRadioRange()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the range
* @param minRadio value, without the home repeater
* @param maxRadio value, without the home repeater
*/
public NXDNTypeDRadioRange(int homeRepeater, int minRadio, int maxRadio)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, minRadio),
NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, maxRadio));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_RADIO_ID_RANGE;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMinRadio());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
int min = getMinIdentifier();
int max = getMaxIdentifier();
setMinRadio(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, min));
setMaxRadio(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, max));
}
@JsonIgnore
@Override
public int getMinIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMinRadio());
}
@Override
public void setMinIdentifier(int identifier)
{
setMinRadio(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@JsonIgnore
@Override
public int getMaxIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMaxRadio());
}
@Override
public void setMaxIdentifier(int identifier)
{
setMaxRadio(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
/**
* Valid when the base range is valid and both ends of the range use the same home repeater.
*/
@Override
public boolean isValid()
{
return super.isValid() && getHomeRepeater() == NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMaxRadio());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Radio ID Range:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMinIdentifier(), true));
sb.append(" to ").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMaxIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,96 @@
/*
* *****************************************************************************
* 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.alias.id.talkgroup;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeD;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D talkgroup. The value is the over-the-air 16-bit identifier that combines the home repeater and talkgroup
* so that it matches the value of an NXDN Type-D talkgroup identifier.
*/
public class NXDNTypeDTalkgroup extends Talkgroup implements INXDNTypeD
{
public NXDNTypeDTalkgroup()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the talkgroup
* @param talkgroup value, without the home repeater
*/
public NXDNTypeDTalkgroup(int homeRepeater, int talkgroup)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, talkgroup));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_TALKGROUP;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getValue());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, getIdentifier()));
}
@JsonIgnore
@Override
public int getIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getValue());
}
@Override
public void setIdentifier(int identifier)
{
setValue(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Talkgroup:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,125 @@
/*
* *****************************************************************************
* 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.alias.id.talkgroup;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.INXDNTypeDRange;
import io.github.dsheirer.preference.identifier.talkgroup.NXDNTalkgroupFormatter;
import io.github.dsheirer.protocol.Protocol;
/**
* NXDN Type-D talkgroup range for a single home repeater. The min and max values are the over-the-air 16-bit
* identifiers that combine the home repeater and talkgroup.
*/
public class NXDNTypeDTalkgroupRange extends TalkgroupRange implements INXDNTypeDRange
{
public NXDNTypeDTalkgroupRange()
{
//No-arg JAXB constructor
setProtocol(Protocol.NXDN);
}
/**
* Constructs an instance
* @param homeRepeater for the range
* @param minTalkgroup value, without the home repeater
* @param maxTalkgroup value, without the home repeater
*/
public NXDNTypeDTalkgroupRange(int homeRepeater, int minTalkgroup, int maxTalkgroup)
{
super(Protocol.NXDN, NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, minTalkgroup),
NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, maxTalkgroup));
}
@Override
public AliasIDType getType()
{
return AliasIDType.NXDN_TYPE_D_TALKGROUP_RANGE;
}
@JsonIgnore
@Override
public int getHomeRepeater()
{
return NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMinTalkgroup());
}
@Override
public void setHomeRepeater(int homeRepeater)
{
int min = getMinIdentifier();
int max = getMaxIdentifier();
setMinTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, min));
setMaxTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(homeRepeater, max));
}
@JsonIgnore
@Override
public int getMinIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMinTalkgroup());
}
@Override
public void setMinIdentifier(int identifier)
{
setMinTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
@JsonIgnore
@Override
public int getMaxIdentifier()
{
return NXDNTalkgroupFormatter.getTypeDIdentifier(getMaxTalkgroup());
}
@Override
public void setMaxIdentifier(int identifier)
{
setMaxTalkgroup(NXDNTalkgroupFormatter.toTypeDValue(getHomeRepeater(), identifier));
}
/**
* Valid when the base range is valid and both ends of the range use the same home repeater.
*/
@Override
public boolean isValid()
{
return super.isValid() &&
getHomeRepeater() == NXDNTalkgroupFormatter.getTypeDHomeRepeater(getMaxTalkgroup());
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Type-D Talkgroup Range:").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMinIdentifier(), true));
sb.append(" to ").append(NXDNTalkgroupFormatter.toTypeD(getHomeRepeater(), getMaxIdentifier(), true));
sb.append(" Protocol:").append(getProtocol());
if(!isValid())
{
sb.append(" **NOT VALID**");
}
return sb.toString();
}
}

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

View File

@@ -91,9 +91,9 @@ public abstract class RemoteControl extends CallControl
}
else if(getCallControlOption().hasLocationId() && getLocationIDOption().isDestination())
{
// mDestinationIdentifier = NXDNFullyQualifiedRadioIdentifier.createFrom(getLocationID().getSystem().getValue(),
// getMessage().getInt(IDENTIFIER_OCTET_3));
mDestinationIdentifier = NXDNRadioIdentifier.createFrom(getMessage().getInt(IDENTIFIER_OCTET_3));
// mDestinationIdentifier = NXDNFullyQualifiedRadioIdentifier.createTo(getLocationID().getSystem().getValue(),
// getMessage().getInt(IDENTIFIER_OCTET_5));
mDestinationIdentifier = NXDNRadioIdentifier.createTo(getMessage().getInt(IDENTIFIER_OCTET_5));
}
}

View File

@@ -33,6 +33,9 @@ public class NXDNTalkgroupFormatter extends AbstractIntegerFormatter
public static final int TYPE_D_REPEATER_WIDTH = 2;
public static final int TYPE_D_VALUE_WIDTH = 4;
public static final String TYPE_D_SEPARATOR = "-";
public static final int TYPE_D_IDENTIFIER_BIT_WIDTH = 11;
public static final int TYPE_D_MAX_HOME_REPEATER = 0x1F;
public static final int TYPE_D_MAX_IDENTIFIER = 0x7FF;
public static final int GROUP_DECIMAL_WIDTH = 5;
public static final int UNIT_DECIMAL_WIDTH = 5;
public static final int GROUP_HEXADECIMAL_WIDTH = 4;
@@ -129,6 +132,37 @@ public class NXDNTalkgroupFormatter extends AbstractIntegerFormatter
}
}
/**
* Combines the Type-D home repeater and radio or talkgroup into the over-the-air 16-bit identifier value.
* @param homeRepeater for the ID
* @param identifier radio or talkgroup value
* @return combined value
*/
public static int toTypeDValue(int homeRepeater, int identifier)
{
return (homeRepeater & TYPE_D_MAX_HOME_REPEATER) << TYPE_D_IDENTIFIER_BIT_WIDTH | (identifier & TYPE_D_MAX_IDENTIFIER);
}
/**
* Home repeater from a combined Type-D identifier value
* @param value that combines the home repeater and the radio or talkgroup
* @return home repeater
*/
public static int getTypeDHomeRepeater(int value)
{
return (value >> TYPE_D_IDENTIFIER_BIT_WIDTH) & TYPE_D_MAX_HOME_REPEATER;
}
/**
* Radio or talkgroup from a combined Type-D identifier value
* @param value that combines the home repeater and the radio or talkgroup
* @return radio or talkgroup, without the home repeater
*/
public static int getTypeDIdentifier(int value)
{
return value & TYPE_D_MAX_IDENTIFIER;
}
/**
* Formats a Type-D identifier
* @param homeRepeater for the ID

View File

@@ -0,0 +1,203 @@
/*
* *****************************************************************************
* 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.alias;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadio;
import io.github.dsheirer.alias.id.radio.NXDNTypeDRadioRange;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroup;
import io.github.dsheirer.alias.id.talkgroup.NXDNTypeDTalkgroupRange;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNRadioIdentifier;
import io.github.dsheirer.module.decode.nxdn.identifier.NXDNTalkgroupIdentifier;
import io.github.dsheirer.module.decode.nxdn.layer3.NXDNMessageType;
import io.github.dsheirer.module.decode.nxdn.layer3.call.RemoteControlRequest;
import io.github.dsheirer.protocol.Protocol;
import java.util.List;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class NXDNAliasTest
{
private static final int HOME_REPEATER = 3;
private static final int RAW_TALKGROUP_03_0123 = 6267;
private static final int RAW_RADIO_03_0456 = 6600;
private static Alias alias(AliasID id, AliasList list)
{
Alias alias = new Alias();
alias.setName(id.toString());
alias.addAliasID(id);
list.addAlias(alias);
return alias;
}
private static void assertAlias(Alias expected, AliasList list, Identifier identifier)
{
assertEquals(List.of(expected), list.getAliases(identifier), "Alias for " + identifier);
}
@Test
void standardIdentifiersStillMatch()
{
AliasList list = new AliasList("test");
Alias talkgroup = alias(new Talkgroup(Protocol.NXDN, 123), list);
Alias radio = alias(new Radio(Protocol.NXDN, 456), list);
assertAlias(talkgroup, list, NXDNTalkgroupIdentifier.createTo(123));
assertAlias(radio, list, NXDNRadioIdentifier.createFrom(456));
}
@Test
void typeDTalkgroupMatchesDisplayedRepeaterAndTalkgroup()
{
NXDNTypeDTalkgroup id = new NXDNTypeDTalkgroup(HOME_REPEATER, 123);
assertEquals(RAW_TALKGROUP_03_0123, id.getValue());
assertEquals(HOME_REPEATER, id.getHomeRepeater());
assertEquals(123, id.getIdentifier());
assertTrue(id.isValid());
AliasList list = new AliasList("test");
Alias alias = alias(id, list);
NXDNTalkgroupIdentifier identifier = NXDNTalkgroupIdentifier.createTypeDTo(RAW_TALKGROUP_03_0123);
assertEquals("03-0123", identifier.toString());
assertAlias(alias, list, identifier);
assertTrue(list.getAliases(NXDNTalkgroupIdentifier.createTypeDTo(RAW_TALKGROUP_03_0123 + 2048)).isEmpty(),
"Same talkgroup on another home repeater must not match");
}
@Test
void typeDRadioMatchesDisplayedRepeaterAndRadio()
{
AliasList list = new AliasList("test");
Alias alias = alias(new NXDNTypeDRadio(HOME_REPEATER, 456), list);
assertAlias(alias, list, NXDNRadioIdentifier.createTypeDFrom(RAW_RADIO_03_0456));
assertAlias(alias, list, NXDNRadioIdentifier.createTypeDTo(RAW_RADIO_03_0456));
}
@Test
void typeDRangesMatchOnlyTheirHomeRepeater()
{
AliasList list = new AliasList("test");
Alias talkgroups = alias(new NXDNTypeDTalkgroupRange(HOME_REPEATER, 100, 200), list);
Alias radios = alias(new NXDNTypeDRadioRange(HOME_REPEATER, 400, 500), list);
assertAlias(talkgroups, list, NXDNTalkgroupIdentifier.createTypeDTo(RAW_TALKGROUP_03_0123));
assertAlias(radios, list, NXDNRadioIdentifier.createTypeDFrom(RAW_RADIO_03_0456));
assertTrue(list.getAliases(NXDNTalkgroupIdentifier.createTypeDTo((4 << 11) | 123)).isEmpty());
assertTrue(list.getAliases(NXDNRadioIdentifier.createTypeDFrom((4 << 11) | 456)).isEmpty());
}
@Test
void typeDFieldsCanBeEditedIndependently()
{
NXDNTypeDTalkgroup talkgroup = new NXDNTypeDTalkgroup();
assertEquals(Protocol.NXDN, talkgroup.getProtocol());
talkgroup.setIdentifier(123);
talkgroup.setHomeRepeater(HOME_REPEATER);
assertEquals(RAW_TALKGROUP_03_0123, talkgroup.getValue());
NXDNTypeDRadioRange range = new NXDNTypeDRadioRange();
range.setMinIdentifier(400);
range.setMaxIdentifier(500);
range.setHomeRepeater(HOME_REPEATER);
assertEquals(HOME_REPEATER, range.getHomeRepeater());
assertEquals(400, range.getMinIdentifier());
assertEquals(500, range.getMaxIdentifier());
assertTrue(range.isValid());
}
@Test
void typeDRangeAcrossHomeRepeatersIsNotValid()
{
NXDNTypeDTalkgroupRange range = new NXDNTypeDTalkgroupRange();
range.setMinTalkgroup((3 << 11) | 100);
range.setMaxTalkgroup((4 << 11) | 100);
assertFalse(range.isValid());
}
@Test
void copyRetainsTypeDIdentifierType()
{
for(AliasID id : List.of(new NXDNTypeDTalkgroup(HOME_REPEATER, 123), new NXDNTypeDRadio(HOME_REPEATER, 456),
new NXDNTypeDTalkgroupRange(HOME_REPEATER, 100, 200), new NXDNTypeDRadioRange(HOME_REPEATER, 400, 500)))
{
Alias original = new Alias();
original.setName("original");
original.addAliasID(id);
AliasID copy = AliasFactory.copyOf(original).getAliasIdentifiers().getFirst();
assertNotSame(id, copy);
assertEquals(id.getClass(), copy.getClass());
assertEquals(id.toString(), copy.toString());
}
}
@Test
void typeDIdentifiersRoundTripThroughPlaylistXml() throws Exception
{
XmlMapper mapper = new XmlMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
for(AliasID id : List.of(new NXDNTypeDTalkgroup(HOME_REPEATER, 123), new NXDNTypeDRadio(HOME_REPEATER, 456),
new NXDNTypeDTalkgroupRange(HOME_REPEATER, 100, 200), new NXDNTypeDRadioRange(HOME_REPEATER, 400, 500)))
{
String xml = mapper.writeValueAsString(id);
assertFalse(xml.contains("homeRepeater"), xml);
AliasID restored = mapper.readValue(xml, AliasID.class);
assertEquals(id.getClass(), restored.getClass(), xml);
assertEquals(id.toString(), restored.toString(), xml);
}
}
@Test
void remoteControlDestinationWithLocationIdIsTheDestinationRadio()
{
int source = 456;
int destination = 200;
//Location option values: 0=COMMON, 1=DESTINATION, 2=SOURCE
for(int locationOption = 0; locationOption < 3; locationOption++)
{
CorrectedBinaryMessage message = new CorrectedBinaryMessage(200);
message.load(8, 8, 0x40); //Call control option: has location ID
message.load(16, 3, 4); //Destination is a radio
message.load(24, 16, source);
message.load(40, 16, destination);
message.load(72, 2, locationOption);
RemoteControlRequest request = new RemoteControlRequest(message, 1, NXDNMessageType.CONTROL_OUT_52_CC_REMOTE_CONTROL_REQUEST, 1, null);
NXDNRadioIdentifier radio = assertInstanceOf(NXDNRadioIdentifier.class, request.getDestination());
assertEquals(destination, radio.getValue(), "Location option " + locationOption);
assertEquals(Role.TO, radio.getRole(), "Location option " + locationOption);
assertEquals(source, request.getSource().getValue(), "Location option " + locationOption);
}
}
}