mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 09:37:36 -05:00
Compare commits
26 Commits
v0.6.0-alp
...
v0.6.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c96349fa1c | ||
|
|
f640576cc3 | ||
|
|
77d725a488 | ||
|
|
2c0305f928 | ||
|
|
21fd61d507 | ||
|
|
8fb56a5342 | ||
|
|
5d7b73f876 | ||
|
|
85b2a38058 | ||
|
|
c78517ee75 | ||
|
|
ae116d3dfe | ||
|
|
93252f9a40 | ||
|
|
3c03a4db2e | ||
|
|
d0fcd617aa | ||
|
|
6ca0decbd6 | ||
|
|
ddd9ca933e | ||
|
|
b2cc29569e | ||
|
|
411735772b | ||
|
|
2ffb2963af | ||
|
|
227c88c8a2 | ||
|
|
cbe9c79498 | ||
|
|
1b8c100b43 | ||
|
|
30c5fd64f2 | ||
|
|
3a6b486340 | ||
|
|
ded7335dbf | ||
|
|
005a4f8119 | ||
|
|
1ac49bfa7f |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -53,7 +53,7 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.6.0-alpha7'
|
version = '0.6.0-beta2'
|
||||||
|
|
||||||
//Java 20 is required for this version of the Project Panama preview/incubator feature
|
//Java 20 is required for this version of the Project Panama preview/incubator feature
|
||||||
java {
|
java {
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,22 +19,15 @@
|
|||||||
package io.github.dsheirer.alias;
|
package io.github.dsheirer.alias;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.action.AliasAction;
|
import io.github.dsheirer.alias.action.AliasAction;
|
||||||
import io.github.dsheirer.alias.action.AliasActionType;
|
|
||||||
import io.github.dsheirer.alias.action.beep.BeepAction;
|
import io.github.dsheirer.alias.action.beep.BeepAction;
|
||||||
import io.github.dsheirer.alias.action.clip.ClipAction;
|
import io.github.dsheirer.alias.action.clip.ClipAction;
|
||||||
import io.github.dsheirer.alias.action.script.ScriptAction;
|
import io.github.dsheirer.alias.action.script.ScriptAction;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.AliasIDType;
|
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
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.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
||||||
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.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
@@ -45,11 +38,10 @@ import io.github.dsheirer.alias.id.status.UserStatusID;
|
|||||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class AliasFactory
|
public class AliasFactory
|
||||||
{
|
{
|
||||||
@@ -64,6 +56,11 @@ public class AliasFactory
|
|||||||
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
||||||
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
||||||
return copyBroadcast;
|
return copyBroadcast;
|
||||||
|
case DCS:
|
||||||
|
Dcs originalDcs = (Dcs)id;
|
||||||
|
Dcs copyDcs = new Dcs();
|
||||||
|
copyDcs.setDCSCode(originalDcs.getDCSCode());
|
||||||
|
return copyDcs;
|
||||||
case ESN:
|
case ESN:
|
||||||
Esn originalESN = (Esn)id;
|
Esn originalESN = (Esn)id;
|
||||||
Esn copyESN = new Esn();
|
Esn copyESN = new Esn();
|
||||||
@@ -234,65 +231,4 @@ public class AliasFactory
|
|||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AliasID getAliasID(AliasIDType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case BROADCAST_CHANNEL:
|
|
||||||
return new BroadcastChannel();
|
|
||||||
case ESN:
|
|
||||||
return new Esn();
|
|
||||||
case FLEETSYNC:
|
|
||||||
return new FleetsyncID();
|
|
||||||
case LTR_NET_UID:
|
|
||||||
return new UniqueID();
|
|
||||||
case LOJACK:
|
|
||||||
return new LoJackFunctionAndID();
|
|
||||||
case MDC1200:
|
|
||||||
return new MDC1200ID();
|
|
||||||
case MIN:
|
|
||||||
return new Min();
|
|
||||||
case MPT1327:
|
|
||||||
return new MPT1327ID();
|
|
||||||
case NON_RECORDABLE:
|
|
||||||
return new NonRecordable();
|
|
||||||
case PRIORITY:
|
|
||||||
return new Priority();
|
|
||||||
case RADIO_ID:
|
|
||||||
return new Radio();
|
|
||||||
case RADIO_ID_RANGE:
|
|
||||||
return new RadioRange();
|
|
||||||
case RECORD:
|
|
||||||
return new Record();
|
|
||||||
case TALKGROUP:
|
|
||||||
return new Talkgroup();
|
|
||||||
case TALKGROUP_RANGE:
|
|
||||||
return new TalkgroupRange();
|
|
||||||
case SITE:
|
|
||||||
return new SiteID();
|
|
||||||
case STATUS:
|
|
||||||
return new UserStatusID();
|
|
||||||
case LEGACY_TALKGROUP:
|
|
||||||
return new LegacyTalkgroupID();
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AliasAction getAliasAction(AliasActionType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case BEEP:
|
|
||||||
return new BeepAction();
|
|
||||||
case CLIP:
|
|
||||||
return new ClipAction();
|
|
||||||
case SCRIPT:
|
|
||||||
return new ScriptAction();
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,6 +21,7 @@ package io.github.dsheirer.alias;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
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.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
@@ -32,6 +33,7 @@ import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
|||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
|
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
|
||||||
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
@@ -41,12 +43,8 @@ import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
|||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneSequence;
|
import io.github.dsheirer.identifier.tone.ToneSequence;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -57,6 +55,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
||||||
@@ -67,6 +69,7 @@ public class AliasList
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
||||||
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
|
private Map<DCSCode,Alias> mDCSCodeAliasMap = new EnumMap<>(DCSCode.class);
|
||||||
private Map<String,Alias> mESNMap = new HashMap<>();
|
private Map<String,Alias> mESNMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
||||||
@@ -178,6 +181,12 @@ public class AliasList
|
|||||||
|
|
||||||
radioRangeAliasList.add(radioRange, alias);
|
radioRangeAliasList.add(radioRange, alias);
|
||||||
break;
|
break;
|
||||||
|
case DCS:
|
||||||
|
if(id instanceof Dcs dcs)
|
||||||
|
{
|
||||||
|
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ESN:
|
case ESN:
|
||||||
String esn = ((Esn)id).getEsn();
|
String esn = ((Esn)id).getEsn();
|
||||||
|
|
||||||
@@ -418,7 +427,7 @@ public class AliasList
|
|||||||
return toList(radioAliasList.getAlias(radio));
|
return toList(radioAliasList.getAlias(radio));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ESN:
|
case DCS:
|
||||||
if(identifier instanceof ESNIdentifier)
|
if(identifier instanceof ESNIdentifier)
|
||||||
{
|
{
|
||||||
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
|
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
|
||||||
@@ -439,9 +448,9 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TONE:
|
case TONE:
|
||||||
if(identifier instanceof ToneIdentifier)
|
if(identifier instanceof ToneIdentifier toneIdentifier)
|
||||||
{
|
{
|
||||||
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
|
ToneSequence toneSequence = toneIdentifier.getValue();
|
||||||
|
|
||||||
if(toneSequence != null && toneSequence.hasTones())
|
if(toneSequence != null && toneSequence.hasTones())
|
||||||
{
|
{
|
||||||
@@ -454,6 +463,15 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(identifier instanceof DCSIdentifier dcsIdentifier)
|
||||||
|
{
|
||||||
|
DCSCode dcsCode = dcsIdentifier.getValue();
|
||||||
|
|
||||||
|
if(dcsCode != null)
|
||||||
|
{
|
||||||
|
return toList(mDCSCodeAliasMap.get(dcsCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * 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 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;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
|
|||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
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.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
||||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
||||||
@@ -54,6 +52,7 @@ import javafx.util.Callback;
|
|||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
||||||
|
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
|
||||||
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
||||||
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
||||||
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * 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 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;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@ import java.util.EnumSet;
|
|||||||
public enum AliasIDType
|
public enum AliasIDType
|
||||||
{
|
{
|
||||||
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
||||||
|
DCS("Digital Coded Squelch (DCS)"),
|
||||||
ESN("ESN"),
|
ESN("ESN"),
|
||||||
INVERT("Audio Inversion"),
|
INVERT("Audio Inversion"),
|
||||||
LOJACK("LoJack"),
|
LOJACK("LoJack"),
|
||||||
|
|||||||
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.alias.id.dcs;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
|
import io.github.dsheirer.alias.id.AliasIDType;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digital Coded Squelch (DCS) tone identifier
|
||||||
|
*/
|
||||||
|
public class Dcs extends AliasID implements Comparable<Dcs>
|
||||||
|
{
|
||||||
|
private DCSCode mDCSCode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AliasIDType getType()
|
||||||
|
{
|
||||||
|
return AliasIDType.DCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(AliasID id)
|
||||||
|
{
|
||||||
|
if(isValid() && id instanceof Dcs other)
|
||||||
|
{
|
||||||
|
return other.isValid() && getDCSCode().equals(other.getDCSCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCS code
|
||||||
|
* @return DCS code or null.
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "code")
|
||||||
|
public DCSCode getDCSCode()
|
||||||
|
{
|
||||||
|
return mDCSCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the DCS code value.
|
||||||
|
* @param dcsCode to set
|
||||||
|
*/
|
||||||
|
public void setDCSCode(DCSCode dcsCode)
|
||||||
|
{
|
||||||
|
mDCSCode = dcsCode;
|
||||||
|
updateValueProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return mDCSCode != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAudioIdentifier()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if(isValid())
|
||||||
|
{
|
||||||
|
return getDCSCode().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "DCS-Invalid - No Tone Selected";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Dcs o)
|
||||||
|
{
|
||||||
|
if(isValid())
|
||||||
|
{
|
||||||
|
if(o.isValid())
|
||||||
|
{
|
||||||
|
return getDCSCode().compareTo(o.getDCSCode());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.buffer.INativeBufferProvider;
|
|||||||
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
|
import io.github.dsheirer.log.LoggingSuppressor;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||||
@@ -65,6 +66,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class PolyphaseChannelManager implements ISourceEventProcessor
|
public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||||
{
|
{
|
||||||
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
|
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LoggerFactory.getLogger(PolyphaseChannelManager.class));
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||||
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
||||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||||
@@ -203,7 +205,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
catch(IllegalArgumentException iae)
|
catch(IllegalArgumentException iae)
|
||||||
{
|
{
|
||||||
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
|
LOGGING_SUPPRESSOR.error(iae.getMessage(), 3, "Couldn't allocate channel. " + iae.getMessage());
|
||||||
|
channelSource = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal 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.edac;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
|
||||||
|
* <p>
|
||||||
|
* See ETSI TS 102 361-1 B.2.2.1 and B.2.2.2
|
||||||
|
*/
|
||||||
|
public class BPTC_16_2
|
||||||
|
{
|
||||||
|
private static final int[] DEINTERLEAVE = new int[]{0, 24, 1, 25, 2, 26, 3, 27, 4, 28, 5, 29, 6, 30, 7, 31, 8, 16,
|
||||||
|
9, 17, 10, 18, 11, 19, 12, 20, 13, 21, 14, 22, 15, 23};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unscramble and perform FEC checks per paragraph B.2.2.1 for Non-Reverse Channel Single Burst
|
||||||
|
*
|
||||||
|
* @param message with 32 interleaved bits.
|
||||||
|
* @return descrambled and error checked message or null if the process fails or there are too many errors.
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage decodeShortBurst(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage deinterleaved = deinterleave(message);
|
||||||
|
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
|
||||||
|
|
||||||
|
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for even parity. Bits 0-15 should be the same as bits 16-31.
|
||||||
|
for(int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
if(deinterleaved.get(x) ^ deinterleaved.get(x + 16))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unscramble and perform FEC checks per paragraph B.2.2.2 for Reverse Channel Single Burst
|
||||||
|
*
|
||||||
|
* @param binaryMessage with 32 interleaved bits.
|
||||||
|
* @return descrambled and error checked message or null if the process fails or there are too many errors.
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage decodeReverseChannel(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage deinterleaved = deinterleave(message);
|
||||||
|
System.out.println(" DEINTER: " + deinterleaved.toHexString());
|
||||||
|
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
|
||||||
|
System.out.println(" DECODED: " + deinterleaved.toHexString());
|
||||||
|
System.out.println("FEC:" + fec);
|
||||||
|
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for odd parity. Bits 0-15 should be opposite of bits 16-31.
|
||||||
|
for(int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
if(deinterleaved.get(x) == deinterleaved.get(x + 16))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs deinterleave of the interleaved message.
|
||||||
|
*
|
||||||
|
* @param original to deinterleave
|
||||||
|
* @return deinterleaved message
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage original)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage delinterleaved = new CorrectedBinaryMessage(32);
|
||||||
|
for(int x = 0; x < 32; x++)
|
||||||
|
{
|
||||||
|
if(original.get(x))
|
||||||
|
{
|
||||||
|
delinterleaved.set(DEINTERLEAVE[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return delinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
String[] msgs = new String[]{"05030A03", "35003A00", "1C6D2C9E"};
|
||||||
|
|
||||||
|
for(String msg : msgs)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage original = new CorrectedBinaryMessage(BinaryMessage.loadHex(msg));
|
||||||
|
System.out.println("ORIGINAL: " + original.toHexString());
|
||||||
|
CorrectedBinaryMessage decoded = decodeReverseChannel(original);
|
||||||
|
System.out.println("-------------------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -226,17 +226,19 @@ public class CRCUtil
|
|||||||
{
|
{
|
||||||
mLog.debug("Starting");
|
mLog.debug("Starting");
|
||||||
|
|
||||||
//DMR message
|
long poly = 0x13l;
|
||||||
// String raw = "100110010001000000001001000000001111011011010000011001010000000000000000000000001100001000001011";
|
long[] checksums = generate(32, 4, poly, 0, true);
|
||||||
String raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101001001111010001110";
|
|
||||||
mLog.debug(raw);
|
|
||||||
raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101000000000000000000";
|
|
||||||
BinaryMessage message = BinaryMessage.load(raw);
|
|
||||||
mLog.debug(message.toString());
|
|
||||||
|
|
||||||
long polynomial = 0x11021l;
|
StringBuilder sb = new StringBuilder();
|
||||||
decode(message, 0, 80, polynomial, 16);
|
sb.append("private static int[] CHECKSUMS = new int[]{");
|
||||||
mLog.debug(message.toString());
|
for(long checksum: checksums)
|
||||||
mLog.debug("Finished");
|
{
|
||||||
|
sb.append("0x").append(Long.toHexString(checksum).toUpperCase());
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("};");
|
||||||
|
|
||||||
|
System.out.println("Checksums:\n" + sb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
@@ -5,27 +24,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2014 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/>
|
|
||||||
* -----------------------------------------------------------------------
|
|
||||||
* Galois24 decoder based on Hank Wallace's tutorial/algorithm located at:
|
|
||||||
* http://www.aqdi.com/golay.htm
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Galois 24/12/7 decoder
|
* Galois 24/12/7 decoder
|
||||||
*/
|
*/
|
||||||
@@ -160,4 +158,17 @@ public class Golay24
|
|||||||
|
|
||||||
return (checksum ^ calculated);
|
return (checksum ^ calculated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20"));
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0"));
|
||||||
|
CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00"));
|
||||||
|
|
||||||
|
System.out.println("M:" + bm.toHexString());
|
||||||
|
int a = Golay24.checkAndCorrect(bm, 0);
|
||||||
|
System.out.println("M:" + bm.toHexString());
|
||||||
|
|
||||||
|
System.out.println("A:" + a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,22 +22,9 @@ import io.github.dsheirer.source.ISourceEventProcessor;
|
|||||||
import io.github.dsheirer.source.InvalidFrequencyException;
|
import io.github.dsheirer.source.InvalidFrequencyException;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import net.miginfocom.swing.MigLayout;
|
|
||||||
import org.apache.commons.math3.util.FastMath;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Cursor;
|
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.Image;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.awt.Toolkit;
|
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.KeyListener;
|
import java.awt.event.KeyListener;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
@@ -49,27 +36,39 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom frequency controller for displaying or editing frequency values in MHz.
|
||||||
|
*/
|
||||||
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
|
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
|
||||||
|
|
||||||
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
|
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
|
||||||
|
|
||||||
private Color mHighlightColor = Color.YELLOW;
|
private Color mHighlightColor = Color.YELLOW;
|
||||||
|
|
||||||
private long mFrequency;
|
private long mFrequency;
|
||||||
|
|
||||||
private Cursor mBlankCursor;
|
|
||||||
|
|
||||||
private HashMap<Integer,Digit> mDigits = new HashMap<Integer,Digit>();
|
private HashMap<Integer,Digit> mDigits = new HashMap<Integer,Digit>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
public JFrequencyControl()
|
public JFrequencyControl()
|
||||||
{
|
{
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the control/layout.
|
||||||
|
*/
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
setLayout(new MigLayout("insets 0 0 0 0", "[]0[]", ""));
|
setLayout(new MigLayout("insets 0 0 0 0", "[]0[]", ""));
|
||||||
@@ -104,19 +103,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a blank cursor to use when the mouse is over the digits
|
|
||||||
*/
|
|
||||||
//Create an empty byte array
|
|
||||||
byte[] imageByte = new byte[0];
|
|
||||||
|
|
||||||
//Create image for cursor using empty array
|
|
||||||
Image cursorImage = Toolkit.getDefaultToolkit().createImage(imageByte);
|
|
||||||
|
|
||||||
mBlankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "cursor");
|
|
||||||
revalidate();
|
revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the control.
|
||||||
|
* @param enabled true if this component should be enabled, false otherwise
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean enabled)
|
public void setEnabled(boolean enabled)
|
||||||
{
|
{
|
||||||
@@ -157,11 +150,18 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current frequency value for the control.
|
||||||
|
* @return frequency
|
||||||
|
*/
|
||||||
public long getFrequency()
|
public long getFrequency()
|
||||||
{
|
{
|
||||||
return mFrequency;
|
return mFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the frequency value according to the manually changed digit value.
|
||||||
|
*/
|
||||||
private void updateFrequency()
|
private void updateFrequency()
|
||||||
{
|
{
|
||||||
long frequency = 0;
|
long frequency = 0;
|
||||||
@@ -174,14 +174,15 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
mFrequency = frequency;
|
mFrequency = frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires a source changed event
|
||||||
|
* @throws SourceException of there's an error
|
||||||
|
*/
|
||||||
private void fireSourceChanged() throws SourceException
|
private void fireSourceChanged() throws SourceException
|
||||||
{
|
{
|
||||||
updateFrequency();
|
updateFrequency();
|
||||||
|
|
||||||
Iterator<ISourceEventProcessor> it = mProcessors.iterator();
|
Iterator<ISourceEventProcessor> it = mProcessors.iterator();
|
||||||
|
|
||||||
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
|
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
|
||||||
|
|
||||||
while(it.hasNext())
|
while(it.hasNext())
|
||||||
{
|
{
|
||||||
it.next().process(event);
|
it.next().process(event);
|
||||||
@@ -217,12 +218,21 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
mProcessors.remove(processor);
|
mProcessors.remove(processor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom single digit value editor.
|
||||||
|
*/
|
||||||
public class Digit extends JTextField
|
public class Digit extends JTextField
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private int mPower = 0;
|
private int mPower = 0;
|
||||||
private long mValue = 0;
|
private long mValue = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param position of the digit where a position of 1 is the ones unit of MHz and a position of -1 is the tenths
|
||||||
|
* unit of MHz.
|
||||||
|
* @throws ParseException if there is an error
|
||||||
|
*/
|
||||||
private Digit(int position) throws ParseException
|
private Digit(int position) throws ParseException
|
||||||
{
|
{
|
||||||
super("0");
|
super("0");
|
||||||
@@ -236,6 +246,11 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
this.addMouseWheelListener(listener);
|
this.addMouseWheelListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a custom tooltip for the current hover-over digit.
|
||||||
|
* @param power of the digit in units of 10
|
||||||
|
* @return custom tooptip.
|
||||||
|
*/
|
||||||
private static String getTooltip(int power)
|
private static String getTooltip(int power)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -288,23 +303,32 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
{
|
{
|
||||||
//Strip the digits higher than this one
|
//Strip the digits higher than this one
|
||||||
long lower = frequency % (long)(FastMath.pow(10, mPower + 1));
|
long lower = frequency % (long)(FastMath.pow(10, mPower + 1));
|
||||||
|
|
||||||
//Set the value to int value of dividing by 10 to this power
|
//Set the value to int value of dividing by 10 to this power
|
||||||
long value = (long)(lower / (long)(FastMath.pow(10, mPower)));
|
long value = (long)(lower / (long)(FastMath.pow(10, mPower)));
|
||||||
|
|
||||||
set(value, fireChangeEvent);
|
set(value, fireChangeEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency value.
|
||||||
|
* @return frequency value.
|
||||||
|
*/
|
||||||
public long getFrequency()
|
public long getFrequency()
|
||||||
{
|
{
|
||||||
return mValue * (long)FastMath.pow(10, mPower);
|
return mValue * (long)FastMath.pow(10, mPower);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the unit value and fires a change event
|
||||||
|
*/
|
||||||
public void increment()
|
public void increment()
|
||||||
{
|
{
|
||||||
increment(true);
|
increment(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the unit value and fires a change event
|
||||||
|
* @param fireChangeEvent true to fire an event
|
||||||
|
*/
|
||||||
public void increment(boolean fireChangeEvent)
|
public void increment(boolean fireChangeEvent)
|
||||||
{
|
{
|
||||||
if(isEnabled())
|
if(isEnabled())
|
||||||
@@ -313,11 +337,18 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrements the unit value and fires a change event.
|
||||||
|
*/
|
||||||
public void decrement()
|
public void decrement()
|
||||||
{
|
{
|
||||||
decrement(true);
|
decrement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrements the unit value and optionally fires a change event.
|
||||||
|
* @param fireChangeEvent set to true to fire an event
|
||||||
|
*/
|
||||||
public void decrement(boolean fireChangeEvent)
|
public void decrement(boolean fireChangeEvent)
|
||||||
{
|
{
|
||||||
if(isEnabled())
|
if(isEnabled())
|
||||||
@@ -349,7 +380,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
while(mValue < 0)
|
while(mValue < 0)
|
||||||
{
|
{
|
||||||
mValue += 10;
|
mValue += 10;
|
||||||
|
|
||||||
Digit nextHigherDigit = mDigits.get(mPower + 1);
|
Digit nextHigherDigit = mDigits.get(mPower + 1);
|
||||||
|
|
||||||
if(nextHigherDigit != null)
|
if(nextHigherDigit != null)
|
||||||
@@ -363,7 +393,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
while(mValue > 9)
|
while(mValue > 9)
|
||||||
{
|
{
|
||||||
mValue -= 10;
|
mValue -= 10;
|
||||||
|
|
||||||
Digit nextHigherDigit = mDigits.get(mPower + 1);
|
Digit nextHigherDigit = mDigits.get(mPower + 1);
|
||||||
|
|
||||||
if(nextHigherDigit != null)
|
if(nextHigherDigit != null)
|
||||||
@@ -403,10 +432,8 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(se instanceof InvalidFrequencyException)
|
if(se instanceof InvalidFrequencyException ife)
|
||||||
{
|
{
|
||||||
InvalidFrequencyException ife = (InvalidFrequencyException)se;
|
|
||||||
|
|
||||||
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
|
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
|
||||||
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
|
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
|
||||||
}
|
}
|
||||||
@@ -415,13 +442,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
|
|
||||||
|
|
||||||
setText(String.valueOf(mValue));
|
setText(String.valueOf(mValue));
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Listener implements KeyListener,
|
/**
|
||||||
MouseListener,
|
* Multiple Listener interface implementation
|
||||||
MouseWheelListener
|
*/
|
||||||
|
private class Listener implements KeyListener, MouseListener, MouseWheelListener
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void keyReleased(KeyEvent e)
|
public void keyReleased(KeyEvent e)
|
||||||
@@ -503,14 +530,9 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e)
|
public void keyPressed(KeyEvent e) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyTyped(KeyEvent e)
|
public void keyTyped(KeyEvent e) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e)
|
public void mouseClicked(MouseEvent e)
|
||||||
@@ -528,21 +550,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mousePressed(MouseEvent e)
|
public void mousePressed(MouseEvent e) {}
|
||||||
{
|
public void mouseReleased(MouseEvent e) {}
|
||||||
}
|
|
||||||
|
|
||||||
public void mouseReleased(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseEntered(MouseEvent e)
|
public void mouseEntered(MouseEvent e)
|
||||||
{
|
{
|
||||||
Digit.this.setBackground(mHighlightColor);
|
Digit.this.setBackground(mHighlightColor);
|
||||||
|
|
||||||
setCursor(mBlankCursor);
|
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,9 +564,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
public void mouseExited(MouseEvent e)
|
public void mouseExited(MouseEvent e)
|
||||||
{
|
{
|
||||||
Digit.this.setBackground(Color.WHITE);
|
Digit.this.setBackground(Color.WHITE);
|
||||||
|
|
||||||
setCursor(Cursor.getDefaultCursor());
|
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -459,6 +459,12 @@ public class DMRRecordingViewer extends VBox
|
|||||||
if(mFindText == null)
|
if(mFindText == null)
|
||||||
{
|
{
|
||||||
mFindText = new TextField();
|
mFindText = new TextField();
|
||||||
|
mFindText.setOnKeyPressed(event -> {
|
||||||
|
if(event.getCode().equals(KeyCode.ENTER))
|
||||||
|
{
|
||||||
|
getFindButton().fire();
|
||||||
|
}
|
||||||
|
});
|
||||||
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * 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 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;
|
package io.github.dsheirer.gui.playlist.alias;
|
||||||
@@ -37,6 +34,7 @@ import io.github.dsheirer.alias.action.script.ScriptAction;
|
|||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.AliasIDType;
|
import io.github.dsheirer.alias.id.AliasIDType;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
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.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
@@ -63,6 +61,11 @@ import io.github.dsheirer.preference.PreferenceType;
|
|||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
@@ -106,12 +109,6 @@ import org.controlsfx.control.ToggleSwitch;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor for configuring individual aliases
|
* Editor for configuring individual aliases
|
||||||
*/
|
*/
|
||||||
@@ -717,6 +714,7 @@ public class AliasItemEditor extends Editor<Alias>
|
|||||||
Menu nbfmMenu = new ProtocolMenu(Protocol.NBFM);
|
Menu nbfmMenu = new ProtocolMenu(Protocol.NBFM);
|
||||||
nbfmMenu.getItems().add(new AddTalkgroupItem(Protocol.NBFM));
|
nbfmMenu.getItems().add(new AddTalkgroupItem(Protocol.NBFM));
|
||||||
nbfmMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.NBFM));
|
nbfmMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.NBFM));
|
||||||
|
nbfmMenu.getItems().add(new AddDcsItem());
|
||||||
|
|
||||||
Menu passportMenu = new ProtocolMenu(Protocol.PASSPORT);
|
Menu passportMenu = new ProtocolMenu(Protocol.PASSPORT);
|
||||||
passportMenu.getItems().add(new AddTalkgroupItem(Protocol.PASSPORT));
|
passportMenu.getItems().add(new AddTalkgroupItem(Protocol.PASSPORT));
|
||||||
@@ -1281,6 +1279,24 @@ public class AliasItemEditor extends Editor<Alias>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Digital Coded Squelch (DCS) alias identifier menu item
|
||||||
|
*/
|
||||||
|
public class AddDcsItem extends MenuItem
|
||||||
|
{
|
||||||
|
public AddDcsItem()
|
||||||
|
{
|
||||||
|
super("Digital Coded Squelch (DCS)");
|
||||||
|
setOnAction(event -> {
|
||||||
|
Dcs dcs = new Dcs();
|
||||||
|
getIdentifiersList().getItems().add(dcs);
|
||||||
|
getIdentifiersList().getSelectionModel().select(dcs);
|
||||||
|
getIdentifiersList().scrollTo(dcs);
|
||||||
|
modifiedProperty().set(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Menu Item for adding a new protocol-specific Radio ID alias identifier
|
* Menu Item for adding a new protocol-specific Radio ID alias identifier
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.gui.playlist.alias.identifier;
|
||||||
|
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor for Digital Coded Squelch (DCS) alias identifiers
|
||||||
|
*/
|
||||||
|
public class DcsEditor extends IdentifierEditor<Dcs>
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(DcsEditor.class);
|
||||||
|
private ComboBox<DCSCode> mDCSCodeComboBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public DcsEditor()
|
||||||
|
{
|
||||||
|
GridPane gridPane = new GridPane();
|
||||||
|
gridPane.setHgap(5);
|
||||||
|
|
||||||
|
Label typeLabel = new Label("DCS Code");
|
||||||
|
GridPane.setConstraints(typeLabel, 0, 0);
|
||||||
|
gridPane.getChildren().add(typeLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getDCSCodeComboBox(), 1, 0);
|
||||||
|
gridPane.getChildren().add(getDCSCodeComboBox());
|
||||||
|
|
||||||
|
getChildren().add(gridPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setItem(Dcs item)
|
||||||
|
{
|
||||||
|
super.setItem(item);
|
||||||
|
if(item.isValid())
|
||||||
|
{
|
||||||
|
getDCSCodeComboBox().getSelectionModel().select(item.getDCSCode());
|
||||||
|
}
|
||||||
|
modifiedProperty().set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save()
|
||||||
|
{
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combo-box loaded with DCS codes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ComboBox<DCSCode> getDCSCodeComboBox()
|
||||||
|
{
|
||||||
|
if(mDCSCodeComboBox == null)
|
||||||
|
{
|
||||||
|
mDCSCodeComboBox = new ComboBox<>();
|
||||||
|
mDCSCodeComboBox.getItems().addAll(DCSCode.STANDARD_CODES);
|
||||||
|
mDCSCodeComboBox.getItems().addAll(DCSCode.INVERTED_CODES);
|
||||||
|
mDCSCodeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
getItem().setDCSCode(getDCSCodeComboBox().getSelectionModel().getSelectedItem());
|
||||||
|
modifiedProperty().set(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDCSCodeComboBox;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -35,6 +35,8 @@ public class IdentifierEditorFactory
|
|||||||
{
|
{
|
||||||
switch(type)
|
switch(type)
|
||||||
{
|
{
|
||||||
|
case DCS:
|
||||||
|
return new DcsEditor();
|
||||||
case ESN:
|
case ESN:
|
||||||
return new EsnEditor();
|
return new EsnEditor();
|
||||||
case LOJACK:
|
case LOJACK:
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private ToggleSwitch mUseCompressedTalkgroupsToggle;
|
private ToggleSwitch mUseCompressedTalkgroupsToggle;
|
||||||
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||||
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
||||||
private IntegerTextField mLogicalSlotNumberField;
|
private IntegerTextField mLogicalChannelNumberField;
|
||||||
private FrequencyField mDownlinkFrequencyField;
|
private FrequencyField mDownlinkFrequencyField;
|
||||||
// private FrequencyField mUplinkFrequencyField;
|
// private FrequencyField mUplinkFrequencyField;
|
||||||
private Button mAddTimeslotFrequencyButton;
|
private Button mAddTimeslotFrequencyButton;
|
||||||
@@ -172,7 +172,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
|
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
|
||||||
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
|
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
|
||||||
|
|
||||||
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
|
Label timeslotTableLabel = new Label("Logical Channel Number (LCN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies. LSN = Logical Slot Number");
|
||||||
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
||||||
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
||||||
gridPane.getChildren().add(timeslotTableLabel);
|
gridPane.getChildren().add(timeslotTableLabel);
|
||||||
@@ -194,17 +194,13 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
editorBox.setAlignment(Pos.CENTER_LEFT);
|
editorBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
editorBox.setSpacing(5);
|
editorBox.setSpacing(5);
|
||||||
|
|
||||||
Label lsnLabel = new Label("LSN");
|
Label lcnLabel = new Label("LCN");
|
||||||
editorBox.getChildren().addAll(lsnLabel,getLogicalSlotNumberField());
|
editorBox.getChildren().addAll(lcnLabel, getLogicalChannelNumberField());
|
||||||
|
|
||||||
Label downlinkLabel = new Label("Frequency (MHz)");
|
Label downlinkLabel = new Label("Frequency (MHz)");
|
||||||
downlinkLabel.setPadding(new Insets(0,0,0,5));
|
downlinkLabel.setPadding(new Insets(0,0,0,5));
|
||||||
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
|
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
|
||||||
|
|
||||||
// Label uplinkLabel = new Label("Uplink");
|
|
||||||
// uplinkLabel.setPadding(new Insets(0,0,0,5));
|
|
||||||
// editorBox.getChildren().addAll(uplinkLabel,getUplinkFrequencyField());
|
|
||||||
|
|
||||||
GridPane.setConstraints(editorBox, 0, row, 4, 1);
|
GridPane.setConstraints(editorBox, 0, row, 4, 1);
|
||||||
gridPane.getChildren().add(editorBox);
|
gridPane.getChildren().add(editorBox);
|
||||||
|
|
||||||
@@ -287,20 +283,22 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
|
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
|
||||||
mTimeslotFrequencyTable.setPrefHeight(100.0);
|
mTimeslotFrequencyTable.setPrefHeight(100.0);
|
||||||
|
|
||||||
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LSN");
|
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LCN");
|
||||||
numberColumn.setPrefWidth(70);
|
numberColumn.setPrefWidth(75);
|
||||||
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
|
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
|
||||||
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
|
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
|
||||||
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
|
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
|
||||||
|
|
||||||
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
|
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
|
||||||
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
|
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
|
||||||
downlinkColumn.setPrefWidth(175);
|
downlinkColumn.setPrefWidth(150);
|
||||||
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
|
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
|
||||||
|
|
||||||
// TableColumn<TimeslotFrequency,Number> uplinkColumn = new TableColumn("Uplink (MHz)");
|
TableColumn<TimeslotFrequency,String> lsnColumn = new TableColumn("IDs (TS1/TS2)");
|
||||||
// uplinkColumn.setCellValueFactory(cellData -> cellData.getValue().getUplinkMHz());
|
lsnColumn.setPrefWidth(225);
|
||||||
// mTimeslotFrequencyTable.getColumns().addAll(uplinkColumn);
|
lsnColumn.setCellValueFactory(new PropertyValueFactory<>("logicalSlotNumbers"));
|
||||||
|
mTimeslotFrequencyTable.getColumns().addAll(lsnColumn);
|
||||||
|
|
||||||
|
|
||||||
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
|
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
|
||||||
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
|
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
|
||||||
@@ -317,20 +315,20 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
//Preserve the current modified flag state since setting values in the editor will change it.
|
//Preserve the current modified flag state since setting values in the editor will change it.
|
||||||
boolean modified = modifiedProperty().get();
|
boolean modified = modifiedProperty().get();
|
||||||
|
|
||||||
getLogicalSlotNumberField().setDisable(timeslot == null);
|
getLogicalChannelNumberField().setDisable(timeslot == null);
|
||||||
getDownlinkFrequencyField().setDisable(timeslot == null);
|
getDownlinkFrequencyField().setDisable(timeslot == null);
|
||||||
// getUplinkFrequencyField().setDisable(timeslot == null);
|
// getUplinkFrequencyField().setDisable(timeslot == null);
|
||||||
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
|
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
|
||||||
|
|
||||||
if(timeslot != null)
|
if(timeslot != null)
|
||||||
{
|
{
|
||||||
getLogicalSlotNumberField().set(timeslot.getNumber());
|
getLogicalChannelNumberField().set(timeslot.getNumber());
|
||||||
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
|
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
|
||||||
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
|
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
getLogicalSlotNumberField().set(0);
|
getLogicalChannelNumberField().set(0);
|
||||||
getDownlinkFrequencyField().set(0);
|
getDownlinkFrequencyField().set(0);
|
||||||
// getUplinkFrequencyField().set(0);
|
// getUplinkFrequencyField().set(0);
|
||||||
}
|
}
|
||||||
@@ -412,19 +410,19 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
return mDeleteTimeslotFrequencyButton;
|
return mDeleteTimeslotFrequencyButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntegerTextField getLogicalSlotNumberField()
|
private IntegerTextField getLogicalChannelNumberField()
|
||||||
{
|
{
|
||||||
if(mLogicalSlotNumberField == null)
|
if(mLogicalChannelNumberField == null)
|
||||||
{
|
{
|
||||||
mLogicalSlotNumberField = new IntegerTextField();
|
mLogicalChannelNumberField = new IntegerTextField();
|
||||||
mLogicalSlotNumberField.setDisable(true);
|
mLogicalChannelNumberField.setDisable(true);
|
||||||
mLogicalSlotNumberField.setPrefWidth(65);
|
mLogicalChannelNumberField.setPrefWidth(65);
|
||||||
mLogicalSlotNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
|
mLogicalChannelNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
if(selected != null)
|
if(selected != null)
|
||||||
{
|
{
|
||||||
Integer value = mLogicalSlotNumberField.get();
|
Integer value = mLogicalChannelNumberField.get();
|
||||||
|
|
||||||
if(value != null)
|
if(value != null)
|
||||||
{
|
{
|
||||||
@@ -436,7 +434,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return mLogicalSlotNumberField;
|
return mLogicalChannelNumberField;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FrequencyField getDownlinkFrequencyField()
|
private FrequencyField getDownlinkFrequencyField()
|
||||||
@@ -610,8 +608,8 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
getTimeslotTable().setDisable(config == null);
|
getTimeslotTable().setDisable(config == null);
|
||||||
getAddTimeslotFrequencyButton().setDisable(config == null);
|
getAddTimeslotFrequencyButton().setDisable(config == null);
|
||||||
getDeleteTimeslotFrequencyButton().setDisable(true);
|
getDeleteTimeslotFrequencyButton().setDisable(true);
|
||||||
getLogicalSlotNumberField().set(0);
|
getLogicalChannelNumberField().set(0);
|
||||||
getLogicalSlotNumberField().setDisable(true);
|
getLogicalChannelNumberField().setDisable(true);
|
||||||
getDownlinkFrequencyField().set(0);
|
getDownlinkFrequencyField().set(0);
|
||||||
getDownlinkFrequencyField().setDisable(true);
|
getDownlinkFrequencyField().setDisable(true);
|
||||||
// getUplinkFrequencyField().set(0);
|
// getUplinkFrequencyField().set(0);
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -41,13 +41,12 @@ import io.github.dsheirer.rrapi.type.Tag;
|
|||||||
import io.github.dsheirer.rrapi.type.Talkgroup;
|
import io.github.dsheirer.rrapi.type.Talkgroup;
|
||||||
import io.github.dsheirer.rrapi.type.Type;
|
import io.github.dsheirer.rrapi.type.Type;
|
||||||
import io.github.dsheirer.rrapi.type.Voice;
|
import io.github.dsheirer.rrapi.type.Voice;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for decoding type, flavor and voice for systems and formatting talkgroups according to user preferences.
|
* Utility class for decoding type, flavor and voice for systems and formatting talkgroups according to user preferences.
|
||||||
@@ -374,16 +373,10 @@ public class RadioReferenceDecoder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int lsn = (lcn - 1) * 2 + 1;
|
TimeslotFrequency timeslotFrequency = new TimeslotFrequency();
|
||||||
TimeslotFrequency timeslot1Frequency = new TimeslotFrequency();
|
timeslotFrequency.setNumber(lcn);
|
||||||
timeslot1Frequency.setNumber(lsn);
|
timeslotFrequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
||||||
timeslot1Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
frequencies.add(timeslotFrequency);
|
||||||
frequencies.add(timeslot1Frequency);
|
|
||||||
|
|
||||||
TimeslotFrequency timeslot2Frequency = new TimeslotFrequency();
|
|
||||||
timeslot2Frequency.setNumber(lsn + 1);
|
|
||||||
timeslot2Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
|
||||||
frequencies.add(timeslot2Frequency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return frequencies;
|
return frequencies;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public enum Form
|
|||||||
CHANNEL_DESCRIPTOR,
|
CHANNEL_DESCRIPTOR,
|
||||||
CHANNEL_NAME,
|
CHANNEL_NAME,
|
||||||
CHANNEL_FREQUENCY,
|
CHANNEL_FREQUENCY,
|
||||||
|
DCS,
|
||||||
DECODER_TYPE,
|
DECODER_TYPE,
|
||||||
DTMF,
|
DTMF,
|
||||||
ENCRYPTION_KEY,
|
ENCRYPTION_KEY,
|
||||||
|
|||||||
@@ -213,7 +213,12 @@ public enum DCSCode
|
|||||||
/**
|
/**
|
||||||
* Inverted DCS codes
|
* Inverted DCS codes
|
||||||
*/
|
*/
|
||||||
private static final EnumSet<DCSCode> INVERTED_CODES = EnumSet.range(I023, I754);
|
public static final EnumSet<DCSCode> INVERTED_CODES = EnumSet.range(I023, I754);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard DCS codes
|
||||||
|
*/
|
||||||
|
public static final EnumSet<DCSCode> STANDARD_CODES = EnumSet.range(N023,N754);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup map for quickly finding a DCS code from the transmitted value.
|
* Lookup map for quickly finding a DCS code from the transmitted value.
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class DCSMessage extends Message
|
|||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "Digital Coded Squelch (DCS) Detected: " + mDCSCode.name();
|
return "Digital Coded Squelch (DCS) Detected: " + mDCSCode.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcem
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.VoteNowAdvice;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.VoteNowAdvice;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.ChannelGrant;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.ChannelGrant;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
||||||
@@ -65,25 +66,33 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoice
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
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.dmr.message.data.packet.UDTShortMessageService;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
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 io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||||
import io.github.dsheirer.util.PacketUtil;
|
import io.github.dsheirer.util.PacketUtil;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
|
||||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -102,7 +111,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
private DecodeEvent mCurrentCallEvent;
|
private DecodeEvent mCurrentCallEvent;
|
||||||
private long mCurrentFrequency;
|
private long mCurrentFrequency;
|
||||||
private boolean mIgnoreCRCChecksums;
|
private boolean mIgnoreCRCChecksums;
|
||||||
private Map<Integer,DecodeEvent> mDetectedCallEventsMap = new TreeMap<>();
|
private Map<DMRChannel,DecodeEvent> mDetectedCallEventsMap = new HashMap<>();
|
||||||
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
||||||
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
||||||
|
|
||||||
@@ -289,7 +298,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
|
||||||
.details("MESSAGE: " + sms.getSMS())
|
.details("MESSAGE: " + sms.getSMS())
|
||||||
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
||||||
.timeslot(sms.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
broadcast(smsEvent);
|
broadcast(smsEvent);
|
||||||
}
|
}
|
||||||
@@ -312,11 +321,51 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(packet.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details("SMS:" + hyteraSmsPacket.getSMS())
|
.details("SMS:" + hyteraSmsPacket.getSMS())
|
||||||
.build();
|
.build();
|
||||||
broadcast(smsEvent);
|
broadcast(smsEvent);
|
||||||
}
|
}
|
||||||
|
//Hytera Radio Registration Service (RRS)
|
||||||
|
else if(packet.getPacket() instanceof HyteraRrsPacket rrs)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("HYTERA RRS REGISTER RADIO:");
|
||||||
|
sb.append(rrs.getDestination());
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(sb.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
|
//Hytera Short Data
|
||||||
|
else if(packet.getPacket() instanceof HyteraShortDataPacket hsdp)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("HYTERA");
|
||||||
|
|
||||||
|
if(hsdp.getPacketSequence().isEncrypted())
|
||||||
|
{
|
||||||
|
HyteraDataEncryptionHeader hdeh = (HyteraDataEncryptionHeader)hsdp.getPacketSequence().getProprietaryDataHeader();
|
||||||
|
sb.append(" ENCRYPTED ALGORITHM:").append(hdeh.getAlgorithm());
|
||||||
|
sb.append(" KEY:").append(hdeh.getKeyId());
|
||||||
|
sb.append(" IV:").append(hdeh.getIV());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
|
||||||
|
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(sb.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
//Unknown Hytera Long Data Service Token Message
|
//Unknown Hytera Long Data Service Token Message
|
||||||
else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket)
|
else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket)
|
||||||
{
|
{
|
||||||
@@ -324,16 +373,52 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(packet.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
.details("HYTERA LONG DATA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||||
.build();
|
.build();
|
||||||
broadcast(unknownTokenEvent);
|
broadcast(unknownTokenEvent);
|
||||||
}
|
}
|
||||||
|
//Motorola ARS
|
||||||
|
else if(packet.getPacket() instanceof ARSPacket ars)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(ars.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
|
//Motorola LRRP
|
||||||
|
else if(packet.getPacket() instanceof LRRPPacket lrrp)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(lrrp.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
|
//Motorola XCMP
|
||||||
|
else if(packet.getPacket() instanceof XCMPPacket xcmp)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(xcmp.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
||||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||||
.timeslot(packet.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(packet.toString())
|
.details(packet.toString())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -344,7 +429,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
if (geoPosition != null) {
|
if (geoPosition != null) {
|
||||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
|
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
|
||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||||
.protocol(Protocol.LRRP)
|
.protocol(Protocol.LRRP)
|
||||||
.location(geoPosition)
|
.location(geoPosition)
|
||||||
.build();
|
.build();
|
||||||
@@ -370,6 +455,19 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(message.getSyncPattern() == DMRSyncPattern.BS_VOICE_FRAME_F && message instanceof VoiceEMBMessage voiceEmb)
|
||||||
|
{
|
||||||
|
if(voiceEmb.hasEmbeddedParameters())
|
||||||
|
{
|
||||||
|
EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters();
|
||||||
|
|
||||||
|
if(embedded.getShortBurst() instanceof EncryptionParameters arc4)
|
||||||
|
{
|
||||||
|
updateEncryptedCall(arc4, true, voiceEmb.getTimestamp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -637,21 +735,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
ChannelGrant dataGrant = (ChannelGrant)csbk;
|
ChannelGrant dataGrant = (ChannelGrant)csbk;
|
||||||
DMRChannel channel = dataGrant.getChannel();
|
DMRChannel channel = dataGrant.getChannel();
|
||||||
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||||
csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
|
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
|
mDetectedCallEventsMap.put(channel, event);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -670,21 +769,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
||||||
DMRChannel channel = tgGrant.getChannel();
|
DMRChannel channel = tgGrant.getChannel();
|
||||||
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
|
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
|
mDetectedCallEventsMap.put(channel, event);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -702,21 +802,23 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
ChannelGrant channelGrant = (ChannelGrant)csbk;
|
ChannelGrant channelGrant = (ChannelGrant)csbk;
|
||||||
DMRChannel channel = channelGrant.getChannel();
|
DMRChannel channel = channelGrant.getChannel();
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel, mergedIdentifiers);
|
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel,
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
|
mDetectedCallEventsMap.put(channel, event);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -747,21 +849,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
|
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
|
||||||
DMRChannel channel = cpdcg.getChannel();
|
DMRChannel channel = cpdcg.getChannel();
|
||||||
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||||
csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
|
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
|
mDetectedCallEventsMap.put(channel, event);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -777,8 +880,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
||||||
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
||||||
.details("Registration Request")
|
.details("Registration Request")
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.timeslot(csbk.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
broadcast(event);
|
broadcast(event);
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
@@ -786,8 +889,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
||||||
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
||||||
.details("Registration Response")
|
.details("Registration Response")
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.timeslot(csbk.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
broadcast(regRespEvent);
|
broadcast(regRespEvent);
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
@@ -798,21 +901,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
||||||
DMRChannel channel = cpvcu.getChannel();
|
DMRChannel channel = cpvcu.getChannel();
|
||||||
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||||
csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
|
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent);
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
|
mDetectedCallEventsMap.put(channel, detectedEvent);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -828,8 +932,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
||||||
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
||||||
.details("TALKGROUP AFFILIATION")
|
.details("TALKGROUP AFFILIATION")
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.timeslot(csbk.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
broadcast(affiliateEvent);
|
broadcast(affiliateEvent);
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
@@ -840,19 +944,20 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel, IdentifierCollection mergedIdentifiers) {
|
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel,
|
||||||
|
IdentifierCollection identifierCollection) {
|
||||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||||
.channel(channel)
|
.channel(channel)
|
||||||
.details(csbk.getOpcode().getLabel())
|
.details(csbk.getOpcode().getLabel())
|
||||||
.identifiers(mergedIdentifiers)
|
.identifiers(identifierCollection)
|
||||||
.timeslot(channel.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
|
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
|
||||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.timeslot(csbk.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details(details)
|
.details(details)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@@ -955,6 +1060,15 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
switch(message.getOpcode())
|
switch(message.getOpcode())
|
||||||
{
|
{
|
||||||
|
case FULL_ENCRYPTION_PARAMETERS:
|
||||||
|
if(message instanceof io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters ep)
|
||||||
|
{
|
||||||
|
if(mCurrentCallEvent != null)
|
||||||
|
{
|
||||||
|
mCurrentCallEvent.setDetails(ep.getDetails());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION:
|
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION:
|
||||||
if(message instanceof CapacityPlusRestChannel)
|
if(message instanceof CapacityPlusRestChannel)
|
||||||
{
|
{
|
||||||
@@ -978,11 +1092,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
|
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
|
||||||
if(message instanceof CapacityPlusGroupVoiceChannelUser cpgvcu)
|
if(message instanceof MotorolaGroupVoiceChannelUser cpgvcu)
|
||||||
{
|
{
|
||||||
updateRestChannel(cpgvcu.getRestChannel());
|
|
||||||
|
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
@@ -1105,7 +1217,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp())
|
DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp())
|
||||||
.identifiers(ic)
|
.identifiers(ic)
|
||||||
.timeslot(message.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details("LOCATION:" + gpsInformation.getGPSLocation())
|
.details("LOCATION:" + gpsInformation.getGPSLocation())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -1121,6 +1233,41 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current call with encryption information.
|
||||||
|
* @param encryptionParameters decoded from the Voice Frame F
|
||||||
|
* @param isGroup true for group or false for individual call.
|
||||||
|
*/
|
||||||
|
private void updateEncryptedCall(EncryptionParameters encryptionParameters, boolean isGroup, long timestamp)
|
||||||
|
{
|
||||||
|
if(mCurrentCallEvent != null)
|
||||||
|
{
|
||||||
|
String details = mCurrentCallEvent.getDetails();;
|
||||||
|
|
||||||
|
if(details == null)
|
||||||
|
{
|
||||||
|
details = encryptionParameters.toString();
|
||||||
|
}
|
||||||
|
else if(!details.contains(encryptionParameters.toString()) && !details.contains("ENCRYPTION"))
|
||||||
|
{
|
||||||
|
details += " " + encryptionParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentCallEvent.setDetails(details);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mCurrentCallEvent = DMRDecodeEvent.builder(isGroup ? DecodeEventType.CALL_GROUP_ENCRYPTED :
|
||||||
|
DecodeEventType.CALL_ENCRYPTED, timestamp)
|
||||||
|
.channel(getCurrentChannel())
|
||||||
|
.details(encryptionParameters.toString())
|
||||||
|
.identifiers(getIdentifierCollection().copyOf())
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.build();
|
||||||
|
broadcast(mCurrentCallEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates or creates a current call event.
|
* Updates or creates a current call event.
|
||||||
*
|
*
|
||||||
@@ -1138,13 +1285,14 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
.details(details)
|
.details(details)
|
||||||
.identifiers(getIdentifierCollection().copyOf())
|
.identifiers(getIdentifierCollection().copyOf())
|
||||||
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
broadcast(mCurrentCallEvent);
|
broadcast(mCurrentCallEvent);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(type != DecodeEventType.CALL)
|
if(mCurrentCallEvent.getDetails() == null)
|
||||||
{
|
{
|
||||||
mCurrentCallEvent.setDetails(details);
|
mCurrentCallEvent.setDetails(details);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceSuperFrameProcessor;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -59,6 +61,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
||||||
private DecodeConfigDMR mConfigDMR;
|
private DecodeConfigDMR mConfigDMR;
|
||||||
|
private VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
|
||||||
|
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
|
||||||
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
||||||
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
||||||
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
||||||
@@ -89,19 +93,53 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
@Override
|
@Override
|
||||||
public void receive(IMessage message)
|
public void receive(IMessage message)
|
||||||
{
|
{
|
||||||
//Enrich messages that carry DMR Logical Slot Number channels with LCN to frequency mappings
|
if(message instanceof FullLCMessage flc)
|
||||||
|
{
|
||||||
|
if(flc.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor1.process(flc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor2.process(flc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(message instanceof VoiceMessage voiceMessage)
|
||||||
|
{
|
||||||
|
if(voiceMessage.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor1.process(voiceMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor2.process(voiceMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid())
|
||||||
|
{
|
||||||
|
if(dmrBurst.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor1.reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor2.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings
|
||||||
if(message instanceof ITimeslotFrequencyReceiver)
|
if(message instanceof ITimeslotFrequencyReceiver)
|
||||||
{
|
{
|
||||||
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
||||||
int[] lsns = receiver.getLogicalTimeslotNumbers();
|
int[] lcns = receiver.getLogicalChannelNumbers();
|
||||||
|
|
||||||
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
||||||
|
|
||||||
for(int lsn: lsns)
|
for(int lcn: lcns)
|
||||||
{
|
{
|
||||||
if(mTimeslotFrequencyMap.containsKey(lsn))
|
if(mTimeslotFrequencyMap.containsKey(lcn))
|
||||||
{
|
{
|
||||||
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lsn));
|
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lcn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * 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 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;
|
package io.github.dsheirer.module.decode.dmr;
|
||||||
@@ -41,14 +38,13 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusCo
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusTrafficChannel;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusTrafficChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.Model;
|
import io.github.dsheirer.module.decode.dmr.message.type.Model;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the network configuration details of a DMR network from the broadcast messages
|
* Tracks the network configuration details of a DMR network from the broadcast messages
|
||||||
@@ -250,7 +246,7 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
ConnectPlusNeighborReport cpnr = (ConnectPlusNeighborReport)csbk;
|
ConnectPlusNeighborReport cpnr = (ConnectPlusNeighborReport)csbk;
|
||||||
mNeighborSites.addAll(cpnr.getNeighbors());
|
mNeighborSites.addAll(cpnr.getNeighbors());
|
||||||
}
|
}
|
||||||
if(mBrand == null)
|
if(mBrand == null || mBrand != BRAND_MOTOROLA_CONNECT_PLUS)
|
||||||
{
|
{
|
||||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||||
}
|
}
|
||||||
@@ -279,7 +275,7 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
*/
|
*/
|
||||||
private void addDmrChannel(DMRChannel dmrChannel)
|
private void addDmrChannel(DMRChannel dmrChannel)
|
||||||
{
|
{
|
||||||
mObservedChannelMap.put(dmrChannel.getLogicalSlotNumber(), dmrChannel);
|
mObservedChannelMap.put(dmrChannel.getValue(), dmrChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset()
|
public void reset()
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
private List<Channel> mManagedTrafficChannels;
|
private List<Channel> mManagedTrafficChannels;
|
||||||
|
|
||||||
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
|
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
|
||||||
private Map<Integer,DMRChannelGrantEvent> mLSNGrantEventMap = new ConcurrentHashMap<>();
|
private Map<DMRChannel,DMRChannelGrantEvent> mChannelGrantEventMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private Listener<ChannelEvent> mChannelEventListener;
|
private Listener<ChannelEvent> mChannelEventListener;
|
||||||
private Listener<IDecodeEvent> mDecodeEventListener;
|
private Listener<IDecodeEvent> mDecodeEventListener;
|
||||||
@@ -322,9 +322,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
|
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
|
||||||
Opcode opcode, long timestamp, boolean encrypted)
|
Opcode opcode, long timestamp, boolean encrypted)
|
||||||
{
|
{
|
||||||
int lsn = channel.getLogicalSlotNumber();
|
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||||
|
|
||||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
|
||||||
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||||
|
|
||||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||||
@@ -335,7 +333,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
.identifiers(identifierCollection)
|
.identifiers(identifierCollection)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
mLSNGrantEventMap.put(lsn, event);
|
mChannelGrantEventMap.put(channel, event);
|
||||||
|
|
||||||
}
|
}
|
||||||
else //Update current event
|
else //Update current event
|
||||||
@@ -355,7 +353,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
.identifiers(identifierCollection)
|
.identifiers(identifierCollection)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
mLSNGrantEventMap.put(lsn, event);
|
mChannelGrantEventMap.put(channel, event);
|
||||||
broadcast(event);
|
broadcast(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -643,37 +641,37 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
*/
|
*/
|
||||||
private void removeCallEvents(long frequency)
|
private void removeCallEvents(long frequency)
|
||||||
{
|
{
|
||||||
List<Integer> lsnsToRemove = new ArrayList<>();
|
List<DMRChannel> channelsToRemove = new ArrayList<>();
|
||||||
|
|
||||||
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
|
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||||
{
|
{
|
||||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
if(entry.getKey().getDownlinkFrequency() == frequency)
|
||||||
{
|
{
|
||||||
lsnsToRemove.add(entry.getKey());
|
channelsToRemove.add(entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Integer lsn: lsnsToRemove)
|
for(DMRChannel channel: channelsToRemove)
|
||||||
{
|
{
|
||||||
mLSNGrantEventMap.remove(lsn);
|
mChannelGrantEventMap.remove(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCallEventDetails(long frequency, String detailFragment)
|
private void updateCallEventDetails(long frequency, String detailFragment)
|
||||||
{
|
{
|
||||||
List<Integer> lsnsToUpdate = new ArrayList<>();
|
List<DMRChannel> channelsToUpdate = new ArrayList<>();
|
||||||
|
|
||||||
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
|
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||||
{
|
{
|
||||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
||||||
{
|
{
|
||||||
lsnsToUpdate.add(entry.getKey());
|
channelsToUpdate.add(entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Integer lsn: lsnsToUpdate)
|
for(DMRChannel channel: channelsToUpdate)
|
||||||
{
|
{
|
||||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||||
|
|
||||||
if(event != null)
|
if(event != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,17 +32,16 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChann
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR AMBE Frame recorder generates call sequence recordings containing JSON representations of audio
|
* DMR AMBE Frame recorder generates call sequence recordings containing JSON representations of audio
|
||||||
* frames, optional encryption and call identifiers.
|
* frames, optional encryption and call identifiers.
|
||||||
@@ -206,8 +205,8 @@ public class DMRCallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
{
|
{
|
||||||
switch(message.getOpcode())
|
switch(message.getOpcode())
|
||||||
{
|
{
|
||||||
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
|
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
|
||||||
if(message instanceof CapacityPlusGroupVoiceChannelUser cpvcu)
|
if(message instanceof MotorolaGroupVoiceChannelUser cpvcu)
|
||||||
{
|
{
|
||||||
if(mCallSequence == null)
|
if(mCallSequence == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * 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 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.channel;
|
package io.github.dsheirer.module.decode.dmr.channel;
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,6 +26,8 @@ import io.github.dsheirer.identifier.Role;
|
|||||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,8 +35,9 @@ import org.apache.commons.lang3.Validate;
|
|||||||
*
|
*
|
||||||
* Note: timeslots are tracked as 1 and 2
|
* Note: timeslots are tracked as 1 and 2
|
||||||
*/
|
*/
|
||||||
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor
|
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor, ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
|
private TimeslotFrequency mTimeslotFrequency;
|
||||||
private int mTimeslot;
|
private int mTimeslot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,13 +59,23 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repeater number for the channel.
|
* Repeater number aka Logical Channel Number (LCN) for the channel.
|
||||||
*/
|
*/
|
||||||
public int getRepeater()
|
public int getChannelNumber()
|
||||||
{
|
{
|
||||||
return getValue();
|
return getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to format the single channel number as an array for TimeslotFrequency matching.
|
||||||
|
* @return channel number as an integer array.
|
||||||
|
*/
|
||||||
|
public int[] getLogicalChannelNumbers()
|
||||||
|
{
|
||||||
|
return new int[]{getChannelNumber()};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timeslot for the channel.
|
* Timeslot for the channel.
|
||||||
* @return timeslot as zero-based index with values in range: 0 or 1.
|
* @return timeslot as zero-based index with values in range: 0 or 1.
|
||||||
@@ -72,36 +85,6 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
return mTimeslot;
|
return mTimeslot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Logical slot number for this channel. LSN is a 1-based index value where repeater one, timeslot 1 is
|
|
||||||
* LSN 1, timeslot 2 is LSN 2, etc.
|
|
||||||
*
|
|
||||||
* Formula: LSN = ((channel - 1) * 2) + timeslot
|
|
||||||
*
|
|
||||||
* @return logical slot number, a 1-based index value
|
|
||||||
*/
|
|
||||||
public int getLogicalSlotNumber()
|
|
||||||
{
|
|
||||||
int repeater = getRepeater();
|
|
||||||
|
|
||||||
if(repeater > 0)
|
|
||||||
{
|
|
||||||
return ((repeater - 1) * 2) + getTimeslot();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of length 1 containing this channel's logical slot number
|
|
||||||
*/
|
|
||||||
public int[] getLSNArray()
|
|
||||||
{
|
|
||||||
int[] logicalSlotNumbers = new int[1];
|
|
||||||
logicalSlotNumbers[0] = getLogicalSlotNumber();
|
|
||||||
return logicalSlotNumbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of timeslots for the DMR channel.
|
* Number of timeslots for the DMR channel.
|
||||||
* @return 2 always.
|
* @return 2 always.
|
||||||
@@ -121,6 +104,13 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "CHAN:" + getChannelNumber() + ":" + getTimeslot();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not implemented
|
* Not implemented
|
||||||
*/
|
*/
|
||||||
@@ -138,4 +128,72 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
{
|
{
|
||||||
throw new IllegalArgumentException("This method is not supported");
|
throw new IllegalArgumentException("This method is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDownlinkFrequency()
|
||||||
|
{
|
||||||
|
if(mTimeslotFrequency != null)
|
||||||
|
{
|
||||||
|
return mTimeslotFrequency.getDownlinkFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getUplinkFrequency()
|
||||||
|
{
|
||||||
|
if(mTimeslotFrequency != null)
|
||||||
|
{
|
||||||
|
return mTimeslotFrequency.getUplinkFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the lsn to frequency mapper value.
|
||||||
|
* @param timeslotFrequency to set
|
||||||
|
*/
|
||||||
|
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
|
||||||
|
{
|
||||||
|
mTimeslotFrequency = timeslotFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
|
{
|
||||||
|
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||||
|
{
|
||||||
|
if(timeslotFrequency.getChannelNumber() == getChannelNumber())
|
||||||
|
{
|
||||||
|
setTimeslotFrequency(timeslotFrequency);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if(this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!(o instanceof DMRChannel that))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!super.equals(o))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getTimeslot() == that.getTimeslot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return Objects.hash(super.hashCode(), getTimeslot());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DMR logical channel. This channel uses a logical channel number and a timeslot.
|
|
||||||
*/
|
|
||||||
public class DMRLogicalChannel extends DMRChannel
|
|
||||||
{
|
|
||||||
private TimeslotFrequency mTimeslotFrequency;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
|
|
||||||
* calculated logical slot value for visual compatibility for users.
|
|
||||||
*
|
|
||||||
* @param channel number or repeater number
|
|
||||||
* @param logicalSlotNumber - zero based index.
|
|
||||||
*/
|
|
||||||
public DMRLogicalChannel(int channel, int timeslot)
|
|
||||||
{
|
|
||||||
super(channel, timeslot);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downlink frequency
|
|
||||||
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long getDownlinkFrequency()
|
|
||||||
{
|
|
||||||
if(mTimeslotFrequency != null)
|
|
||||||
{
|
|
||||||
return mTimeslotFrequency.getDownlinkFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uplink frequency
|
|
||||||
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long getUplinkFrequency()
|
|
||||||
{
|
|
||||||
if(mTimeslotFrequency != null)
|
|
||||||
{
|
|
||||||
return mTimeslotFrequency.getUplinkFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the timeslot frequency mapping
|
|
||||||
* @param timeslotFrequency
|
|
||||||
*/
|
|
||||||
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
|
|
||||||
{
|
|
||||||
mTimeslotFrequency = timeslotFrequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatted channel number
|
|
||||||
*/
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("LSN:").append(getLogicalSlotNumber());
|
|
||||||
// sb.append(" LCN:").append(getValue());
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Logical Slot Number (LSN) channel.
|
||||||
|
*
|
||||||
|
* Note: LSN 1-16 are represented in memory using the following channel and timeslot values:
|
||||||
|
*
|
||||||
|
* LSN: CHANNEL/TIMESLOT
|
||||||
|
* 1: 1/1
|
||||||
|
* 2: 1/2
|
||||||
|
* 3: 2/1
|
||||||
|
* 4: 2/2
|
||||||
|
* 5: 3/1
|
||||||
|
* 6: 3/2
|
||||||
|
* 7: 4/1
|
||||||
|
* 8: 4/2
|
||||||
|
* 9: 5/1
|
||||||
|
* 10: 5/2
|
||||||
|
* 11: 6/1
|
||||||
|
* 12: 6/2
|
||||||
|
* 13: 7/1
|
||||||
|
* 14: 7/2
|
||||||
|
* 15: 8/1
|
||||||
|
* 16: 8/2
|
||||||
|
*/
|
||||||
|
public class DMRLsn extends DMRChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param lsn in range 1 - 16
|
||||||
|
*/
|
||||||
|
public DMRLsn(int lsn)
|
||||||
|
{
|
||||||
|
super(((lsn - 1) / 2) + 1, ((lsn - 1) % 2) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "LSN:" + getLsn() + " LCN:" + getChannelNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logical Slot Number
|
||||||
|
* @return lsn
|
||||||
|
*/
|
||||||
|
public int getLsn()
|
||||||
|
{
|
||||||
|
return ((getChannelNumber() - 1) * 2) + getTimeslot();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,16 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.channel;
|
package io.github.dsheirer.module.decode.dmr.channel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Tier III Trunking Channel
|
* DMR Tier III Trunking channel. This channel uses a logical channel number and a timeslot.
|
||||||
*/
|
*/
|
||||||
public class DMRTier3Channel extends DMRLogicalChannel
|
public class DMRTier3Channel extends DMRChannel
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
|
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
|
||||||
* calculated logical slot value for visual compatibility for users.
|
* calculated logical slot value for visual compatibility for users.
|
||||||
*
|
*
|
||||||
* @param channel number or repeater number
|
* @param channel number or repeater number
|
||||||
* @param timeslot
|
* @param timeslot either 1 or 2.
|
||||||
*/
|
*/
|
||||||
public DMRTier3Channel(int channel, int timeslot)
|
public DMRTier3Channel(int channel, int timeslot)
|
||||||
{
|
{
|
||||||
@@ -18,23 +37,22 @@ public class DMRTier3Channel extends DMRLogicalChannel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical slot number for this channel.
|
* Tier III Channel ID
|
||||||
*
|
* @return channel ID
|
||||||
* Formula: LSN = (channel * 2) + timeslot
|
|
||||||
*
|
|
||||||
* @return logical slot number, a 1-based index value
|
|
||||||
*/
|
*/
|
||||||
@Override
|
public int getChannelId()
|
||||||
public int getLogicalSlotNumber()
|
|
||||||
{
|
{
|
||||||
int repeater = getRepeater();
|
return getChannelNumber() * 2 + getTimeslot();
|
||||||
|
|
||||||
if(repeater > 0)
|
|
||||||
{
|
|
||||||
return ((repeater) * 2) + getTimeslot();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted channel number
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(" LCN:").append(getChannelNumber());
|
||||||
|
sb.append(" CHANID:").append(getChannelId());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Rest Channel logical slot number (LSN).
|
||||||
|
*/
|
||||||
|
public class DmrRestLsn extends DMRLsn
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param lsn in range 1 - 16
|
||||||
|
*/
|
||||||
|
public DmrRestLsn(int lsn)
|
||||||
|
{
|
||||||
|
super(lsn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "REST:" + getLsn();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,9 +24,9 @@ import java.util.List;
|
|||||||
public interface ITimeslotFrequencyReceiver
|
public interface ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Provides the logical slot number(s) that require a matching timeslot frequency mapping
|
* Provides the logical channel number(s) that require a matching timeslot frequency mapping
|
||||||
*/
|
*/
|
||||||
public int[] getLogicalTimeslotNumbers();
|
public int[] getLogicalChannelNumbers();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the list of timeslot frequency mappings to the implementer
|
* Applies the list of timeslot frequency mappings to the implementer
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -31,7 +31,7 @@ import javafx.beans.property.SimpleLongProperty;
|
|||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a timeslot number to a pair of channel frequency values
|
* Maps a logical channel number (LCN) to a pair of channel frequency values
|
||||||
*/
|
*/
|
||||||
public class TimeslotFrequency
|
public class TimeslotFrequency
|
||||||
{
|
{
|
||||||
@@ -73,7 +73,7 @@ public class TimeslotFrequency
|
|||||||
* Downlink Frequency property
|
* Downlink Frequency property
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public LongProperty downlinkFrequencyPropertyProperty()
|
public LongProperty downlinkFrequencyProperty()
|
||||||
{
|
{
|
||||||
return mDownlinkFrequencyProperty;
|
return mDownlinkFrequencyProperty;
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,10 @@ public class TimeslotFrequency
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical slot number (LSN) as a 1-index based counter
|
* Logical channel number (LCN) as a 1-index based counter
|
||||||
|
*
|
||||||
|
* Note: lsn (logical slot number) is a legacy name for this field. The field currently holds the LCN or logical
|
||||||
|
* channel number. To avoid breaking serialization, we leave this field labeled as lsn.
|
||||||
*/
|
*/
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "lsn")
|
@JacksonXmlProperty(isAttribute = true, localName = "lsn")
|
||||||
public int getNumber()
|
public int getNumber()
|
||||||
@@ -109,12 +112,52 @@ public class TimeslotFrequency
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the logical slot number (LSN) as a 1-index start
|
* Logical channel number which is the same value as getNumber.
|
||||||
* @param lsn where LSN 1 is the first slot, 2 the second, etc
|
* @return
|
||||||
*/
|
*/
|
||||||
public void setNumber(int lsn)
|
@JsonIgnore
|
||||||
|
public int getChannelNumber()
|
||||||
{
|
{
|
||||||
mNumberProperty.set(lsn);
|
return getNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public String getLogicalSlotNumbers()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
int channel = getChannelNumber();
|
||||||
|
|
||||||
|
if(channel > 0)
|
||||||
|
{
|
||||||
|
if(channel < 32) //Connect Plus uses 5-bits for LSN for a maximum value of 31
|
||||||
|
{
|
||||||
|
//Cap+ & Con+ Logical Slot Numbers
|
||||||
|
int lsn1 = (channel - 1) * 2 + 1;
|
||||||
|
int lsn2 = (channel - 1) * 2 + 2;
|
||||||
|
sb.append("LSN:").append(lsn1).append("/").append(lsn2).append(" or ");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Tier III channel IDs
|
||||||
|
int chanId1 = channel * 2 + 1;
|
||||||
|
int chanId2 = channel * 2 + 2;
|
||||||
|
sb.append("Tier3 Chan:").append(chanId1).append("/").append(chanId2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append("(empty)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the logical channel number (LCN) as a 1-index start
|
||||||
|
* @param lcn where LCN 1 is logical slot numbers (LSN) 1 and 2, LCN2=LSN3/4, etc.
|
||||||
|
*/
|
||||||
|
public void setNumber(int lcn)
|
||||||
|
{
|
||||||
|
mNumberProperty.set(lcn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,7 +203,7 @@ public class TimeslotFrequency
|
|||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "TIMESLOT LSN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
|
return "LCN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,7 +211,7 @@ public class TimeslotFrequency
|
|||||||
*/
|
*/
|
||||||
public static Callback<TimeslotFrequency,Observable[]> extractor()
|
public static Callback<TimeslotFrequency,Observable[]> extractor()
|
||||||
{
|
{
|
||||||
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyPropertyProperty(),
|
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyProperty(),
|
||||||
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
|
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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;
|
package io.github.dsheirer.module.decode.dmr.message;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
/**
|
/**
|
||||||
@@ -53,4 +73,13 @@ public abstract class DMRBurst extends DMRMessage
|
|||||||
{
|
{
|
||||||
return mSyncPattern;
|
return mSyncPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the sync payload from between the two payload fragments.
|
||||||
|
* @return binary message containing just the sync payload bits (64).
|
||||||
|
*/
|
||||||
|
public BinaryMessage getSyncPayload()
|
||||||
|
{
|
||||||
|
return getMessage().get(SYNC_START, PAYLOAD_2_START);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR message that exposes a service options configuration
|
||||||
|
*/
|
||||||
|
public interface IServiceOptionsProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Service Options
|
||||||
|
* @return service options
|
||||||
|
*/
|
||||||
|
ServiceOptions getServiceOptions();
|
||||||
|
}
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -33,8 +33,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ConfirmedDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ConfirmedDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.DataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.DataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.DefinedShortDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.DefinedShortDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PiHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.RawShortDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.RawShortDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ResponseDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ResponseDataHeader;
|
||||||
@@ -43,9 +43,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.StatusDataHeader
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
|
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
|
||||||
@@ -98,7 +98,7 @@ public class DMRDataMessageFactory
|
|||||||
return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||||
case CHANNEL_CONTROL_ENC_HEADER:
|
case CHANNEL_CONTROL_ENC_HEADER:
|
||||||
case PI_HEADER:
|
case PI_HEADER:
|
||||||
return new HeaderMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
return new PiHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||||
case VOICE_HEADER:
|
case VOICE_HEADER:
|
||||||
return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||||
case DATA_ENC_HEADER:
|
case DATA_ENC_HEADER:
|
||||||
@@ -132,16 +132,16 @@ public class DMRDataMessageFactory
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MotorolaProprietaryDataHeader mprdh = new MotorolaProprietaryDataHeader(pattern,
|
MotorolaDataEncryptionHeader mprdh = new MotorolaDataEncryptionHeader(pattern,
|
||||||
payload, cach, slotType, timestamp, timeslot);
|
payload, cach, slotType, timestamp, timeslot);
|
||||||
mprdh.setValid(valid);
|
mprdh.setValid(valid);
|
||||||
return mprdh;
|
return mprdh;
|
||||||
}
|
}
|
||||||
case HYTERA_68:
|
case HYTERA_68:
|
||||||
HyteraProprietaryDataHeader hpdh = new HyteraProprietaryDataHeader(pattern, payload,
|
HyteraDataEncryptionHeader hsdh = new HyteraDataEncryptionHeader(pattern, payload,
|
||||||
cach, slotType, timestamp, timeslot);
|
cach, slotType, timestamp, timeslot);
|
||||||
hpdh.setValid(valid);
|
hsdh.setValid(valid);
|
||||||
return hpdh;
|
return hsdh;
|
||||||
default:
|
default:
|
||||||
ProprietaryDataHeader pdh = new ProprietaryDataHeader(pattern, payload, cach,
|
ProprietaryDataHeader pdh = new ProprietaryDataHeader(pattern, payload, cach,
|
||||||
slotType, timestamp, timeslot);
|
slotType, timestamp, timeslot);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public abstract class DataMessageWithLinkControl extends DataMessage
|
public abstract class DataMessageWithLinkControl extends DataMessage
|
||||||
{
|
{
|
||||||
private LCMessage mLCMessage;
|
protected LCMessage mLCMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ public class DataBlock1Rate extends DataBlock
|
|||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(hasRAS())
|
||||||
|
{
|
||||||
|
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||||
|
}
|
||||||
if(!isValid())
|
if(!isValid())
|
||||||
{
|
{
|
||||||
sb.append(" [CRC ERROR]");
|
sb.append(" [CRC ERROR]");
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ public class DataBlock1_2Rate extends DataBlock
|
|||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(hasRAS())
|
||||||
|
{
|
||||||
|
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||||
|
}
|
||||||
if(!isValid())
|
if(!isValid())
|
||||||
{
|
{
|
||||||
sb.append(" [CRC ERROR]");
|
sb.append(" [CRC ERROR]");
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ public class DataBlock3_4Rate extends DataBlock
|
|||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(hasRAS())
|
||||||
|
{
|
||||||
|
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||||
|
}
|
||||||
if(!isValid())
|
if(!isValid())
|
||||||
{
|
{
|
||||||
sb.append(" [CRC ERROR]");
|
sb.append(" [CRC ERROR]");
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import java.util.TreeMap;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Control Signalling Block (CSBK) Opcode enumeration
|
* Control Signalling Block (CSBK) Opcode enumeration
|
||||||
*
|
|
||||||
* ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B
|
* ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B
|
||||||
* ETSI TS 102 361-4 DMR Trunking Protocol, Annex B
|
* ETSI TS 102 361-4 DMR Trunking Protocol, Annex B
|
||||||
*/
|
*/
|
||||||
@@ -91,6 +90,8 @@ public enum Opcode
|
|||||||
|
|
||||||
HYTERA_08_ACKNOWLEDGE(Vendor.HYTERA_8, 32, "HYTERA 08 ACKNOWLEDGE"),
|
HYTERA_08_ACKNOWLEDGE(Vendor.HYTERA_8, 32, "HYTERA 08 ACKNOWLEDGE"),
|
||||||
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "HYTERA 08 ANNOUNCEMENT"),
|
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "HYTERA 08 ANNOUNCEMENT"),
|
||||||
|
//CSBKO 44 & 47 observed on Tier3 interleaved in voice group call terminator sequence. 44 was continuously transmitted
|
||||||
|
//and 47 was only transmitted 3x times in succession in the middle of the terminators and CSBKO 44 messages.
|
||||||
HYTERA_08_CSBKO_44(Vendor.HYTERA_8, 44, "HYTERA 08 CSBKO 44"),
|
HYTERA_08_CSBKO_44(Vendor.HYTERA_8, 44, "HYTERA 08 CSBKO 44"),
|
||||||
HYTERA_08_CSBKO_47(Vendor.HYTERA_8, 47, "HYTERA 08 CSBKO 47"),
|
HYTERA_08_CSBKO_47(Vendor.HYTERA_8, 47, "HYTERA 08 CSBKO 47"),
|
||||||
|
|
||||||
@@ -104,9 +105,9 @@ public enum Opcode
|
|||||||
|
|
||||||
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
||||||
|
|
||||||
private Vendor mVendor;
|
private final Vendor mVendor;
|
||||||
private int mValue;
|
private final int mValue;
|
||||||
private String mLabel;
|
private final String mLabel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
|||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
|
||||||
@@ -39,7 +39,6 @@ import java.util.List;
|
|||||||
public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequencyReceiver
|
public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
private static final int[] LC_START_STOP = new int[]{16, 17};
|
private static final int[] LC_START_STOP = new int[]{16, 17};
|
||||||
private static final int TIMESLOT = 18;
|
|
||||||
private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
|
private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
|
||||||
private static final int ASYNC = 24;
|
private static final int ASYNC = 24;
|
||||||
private static final int[] SITE = new int[]{25, 26, 27, 28};
|
private static final int[] SITE = new int[]{25, 26, 27, 28};
|
||||||
@@ -58,7 +57,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
|
|||||||
private static final int[] NEIGHBOR_6_REST = new int[]{76, 77, 78, 79};
|
private static final int[] NEIGHBOR_6_REST = new int[]{76, 77, 78, 79};
|
||||||
|
|
||||||
|
|
||||||
private DMRLogicalChannel mRestChannel;
|
private DmrRestLsn mRestChannel;
|
||||||
private DMRSite mSite;
|
private DMRSite mSite;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
@@ -286,37 +285,23 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
|
|||||||
return getMessage().getInt(REST_LSN);
|
return getMessage().getInt(REST_LSN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest repeater
|
|
||||||
*/
|
|
||||||
public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest timeslot
|
|
||||||
* @return timeslot 1 or 2
|
|
||||||
*/
|
|
||||||
public int getRestTimeslot()
|
|
||||||
{
|
|
||||||
return (getRestLSN() % 2 == 0) ? 2 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Channel
|
* DMR Channel
|
||||||
*/
|
*/
|
||||||
public DMRLogicalChannel getRestChannel()
|
public DmrRestLsn getRestChannel()
|
||||||
{
|
{
|
||||||
if(mRestChannel == null)
|
if(mRestChannel == null)
|
||||||
{
|
{
|
||||||
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
|
mRestChannel = new DmrRestLsn(getRestLSN());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mRestChannel;
|
return mRestChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
return getRestChannel().getLSNArray();
|
return getRestChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -327,13 +312,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
getRestChannel().apply(timeslotFrequencies);
|
||||||
{
|
|
||||||
if(timeslotFrequency.getNumber() == getRestChannel().getLogicalSlotNumber())
|
|
||||||
{
|
|
||||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
|||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
@@ -39,13 +39,11 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
|||||||
private static final int[] BYTE = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
private static final int[] BYTE = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
private static final int[] TWO_BYTES = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
private static final int[] TWO_BYTES = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
private static final int[] SEGMENT_INDICATOR = new int[]{16, 17};
|
private static final int[] SEGMENT_INDICATOR = new int[]{16, 17};
|
||||||
private static final int TIMESLOT = 18;
|
private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
|
||||||
private static final int RESERVED = 19;
|
|
||||||
private static final int[] REST_LSN = new int[]{20, 21, 22, 23};
|
|
||||||
private static final int[] LSN_VOICE_BITMAP = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
private static final int[] LSN_VOICE_BITMAP = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
private static final int LSN_1_8_BITMAP_START = 24;
|
private static final int LSN_1_8_BITMAP_START = 24;
|
||||||
|
|
||||||
private DMRLogicalChannel mRestChannel;
|
private DmrRestLsn mRestChannel;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,7 +78,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
|||||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append(" CSBK CAP+ SITE STATUS REST ").append(getRestChannel());
|
sb.append(" CSBK CAP+ SITE STATUS ").append(getRestChannel());
|
||||||
sb.append(" ").append(getSegmentIndicator());
|
sb.append(" ").append(getSegmentIndicator());
|
||||||
sb.append(" ");
|
sb.append(" ");
|
||||||
|
|
||||||
@@ -277,11 +275,11 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
|||||||
/**
|
/**
|
||||||
* Current rest channel for this site.
|
* Current rest channel for this site.
|
||||||
*/
|
*/
|
||||||
public DMRLogicalChannel getRestChannel()
|
public DmrRestLsn getRestChannel()
|
||||||
{
|
{
|
||||||
if(mRestChannel == null)
|
if(mRestChannel == null)
|
||||||
{
|
{
|
||||||
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
|
mRestChannel = new DmrRestLsn(getRestLSN());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mRestChannel;
|
return mRestChannel;
|
||||||
@@ -297,31 +295,13 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
|||||||
return getMessage().getInt(REST_LSN);
|
return getMessage().getInt(REST_LSN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest Channel Repeater
|
|
||||||
*/
|
|
||||||
public int getRestRepeater()
|
|
||||||
{
|
|
||||||
return (int) Math.ceil(getRestLSN() / 2.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest Channel Timeslot
|
|
||||||
*
|
|
||||||
* @return 1 or 2
|
|
||||||
*/
|
|
||||||
public int getRestTimeslot()
|
|
||||||
{
|
|
||||||
return (getRestLSN() % 2 == 0) ? 2 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical slot numbers that require slot to frequency mappings.
|
* Logical slot numbers that require slot to frequency mappings.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
return getRestChannel().getLSNArray();
|
return getRestChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -332,13 +312,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
getRestChannel().apply(timeslotFrequencies);
|
||||||
{
|
|
||||||
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
|
||||||
{
|
|
||||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
@@ -40,14 +40,13 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
|
|||||||
{
|
{
|
||||||
private static final int[] TARGET_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
private static final int[] TARGET_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||||
32, 33, 34, 35, 36, 37, 38, 39};
|
32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
private static final int[] REPEATER = new int[]{40, 41, 42, 43};
|
private static final int[] LOGICAL_SLOT_NUMBER = new int[]{40, 41, 42, 43, 44};
|
||||||
private static final int[] CHANNEL_GRANT_TIMESLOT = new int[]{44};
|
|
||||||
|
|
||||||
//Analysis: this field correlates to UNKNOWN_FIELD_1(bits: 40-48) in ConnectPlusTerminateChannelGrant.
|
//Analysis: this field correlates to UNKNOWN_FIELD_1(bits: 40-48) in ConnectPlusTerminateChannelGrant.
|
||||||
private static final int[] UNKNOWN_FIELD = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
private static final int[] UNKNOWN_FIELD = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
|
||||||
private RadioIdentifier mTargetRadio;
|
private RadioIdentifier mTargetRadio;
|
||||||
private DMRLogicalChannel mDMRLogicalChannel;
|
private DMRLsn mDmrLsn;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,39 +109,30 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel grant repeater number
|
* Channel grant logical slot number
|
||||||
*/
|
*/
|
||||||
public int getRepeater()
|
public int getLsn()
|
||||||
{
|
{
|
||||||
return getMessage().getInt(REPEATER);
|
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Don't change this ... always subtract 1 to get the repeater LSN
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Channel grant timeslot
|
|
||||||
* @return 1 or 2
|
|
||||||
*/
|
|
||||||
public int getChannelGrantTimeslot()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(CHANNEL_GRANT_TIMESLOT) + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Channel
|
* DMR Channel
|
||||||
*/
|
*/
|
||||||
public DMRLogicalChannel getChannel()
|
public DMRLsn getChannel()
|
||||||
{
|
{
|
||||||
if(mDMRLogicalChannel == null)
|
if(mDmrLsn == null)
|
||||||
{
|
{
|
||||||
mDMRLogicalChannel = new DMRLogicalChannel(getRepeater(), getChannelGrantTimeslot());
|
mDmrLsn = new DMRLsn(getLsn());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mDMRLogicalChannel;
|
return mDmrLsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
return getChannel().getLSNArray();
|
return getChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,13 +142,7 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
getChannel().apply(timeslotFrequencies);
|
||||||
{
|
|
||||||
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
|
|
||||||
{
|
|
||||||
getChannel().setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
|||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
@@ -44,11 +44,10 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
|||||||
private static final int[] MESSAGE_TYPE = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
private static final int[] MESSAGE_TYPE = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
private static final int[] VERSION = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
|
private static final int[] VERSION = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
private static final int[] UNKNOWN = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
private static final int[] UNKNOWN = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||||
57, 58, 59, 60, 61, 62, 63};
|
57, 58, 59, 60, 61, 62};
|
||||||
private static final int[] DATA_REPEATER = new int[]{64, 65, 66, 67};
|
private static final int[] LOGICAL_SLOT_NUMBER = new int[]{63, 64, 65, 66, 67};
|
||||||
private static final int[] DATA_TIMESLOT = new int[]{68};
|
|
||||||
|
|
||||||
private DMRLogicalChannel mDataChannel;
|
private DMRLsn mDataChannel;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,39 +99,30 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data repeater number
|
* Data logical slot numberrepeater number
|
||||||
*/
|
*/
|
||||||
public int getDataRepeater()
|
public int getDataLsn()
|
||||||
{
|
{
|
||||||
return getMessage().getInt(DATA_REPEATER);
|
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Leave this as minus 1
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data Timeslot
|
|
||||||
* @return 1 or 2
|
|
||||||
*/
|
|
||||||
public int getDataTimeslot()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(DATA_TIMESLOT) + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Channel where the data is available
|
* DMR Channel where the data is available
|
||||||
*/
|
*/
|
||||||
public DMRLogicalChannel getDataChannel()
|
public DMRLsn getDataChannel()
|
||||||
{
|
{
|
||||||
if(mDataChannel == null)
|
if(mDataChannel == null)
|
||||||
{
|
{
|
||||||
mDataChannel = new DMRLogicalChannel(getDataRepeater(), getDataTimeslot());
|
mDataChannel = new DMRLsn(getDataLsn());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mDataChannel;
|
return mDataChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
return getDataChannel().getLSNArray();
|
return getDataChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,13 +133,7 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
getDataChannel().apply(timeslotFrequencies);
|
||||||
{
|
|
||||||
if(timeslotFrequency.getNumber() == getDataChannel().getLogicalSlotNumber())
|
|
||||||
{
|
|
||||||
getDataChannel().setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import io.github.dsheirer.identifier.Identifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
@@ -44,13 +44,12 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
|||||||
32, 33, 34, 35, 36, 37, 38, 39};
|
32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
|
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
|
||||||
56, 57, 58, 59, 60, 61, 62, 63};
|
56, 57, 58, 59, 60, 61, 62, 63};
|
||||||
private static final int[] TRAFFIC_CHANNEL_REPEATER = new int[]{64, 65, 66, 67};
|
private static final int[] LOGICAL_SLOT_NUMBER = new int[]{64, 65, 66, 67, 68};
|
||||||
private static final int[] TRAFFIC_CHANNEL_TIMESLOT = new int[]{68};
|
|
||||||
private static final int[] UNKNOWN_FIELD = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
private static final int[] UNKNOWN_FIELD = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
private RadioIdentifier mRadio;
|
private RadioIdentifier mRadio;
|
||||||
private TalkgroupIdentifier mTalkgroup;
|
private TalkgroupIdentifier mTalkgroup;
|
||||||
private DMRLogicalChannel mDMRLogicalChannel;
|
private DMRLsn mDmrLsn;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,37 +126,28 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
|||||||
/**
|
/**
|
||||||
* Traffic channel repeater
|
* Traffic channel repeater
|
||||||
*/
|
*/
|
||||||
public int getTrafficChannelRepeater()
|
public int getTrafficLsn()
|
||||||
{
|
{
|
||||||
return getMessage().getInt(TRAFFIC_CHANNEL_REPEATER);
|
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Always leave as minus 1
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Traffic channel timeslot
|
|
||||||
* @return 1 or 2
|
|
||||||
*/
|
|
||||||
public int getTrafficChannelTimeslot()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(TRAFFIC_CHANNEL_TIMESLOT) + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Channel
|
* DMR Channel
|
||||||
*/
|
*/
|
||||||
public DMRLogicalChannel getChannel()
|
public DMRLsn getChannel()
|
||||||
{
|
{
|
||||||
if(mDMRLogicalChannel == null)
|
if(mDmrLsn == null)
|
||||||
{
|
{
|
||||||
mDMRLogicalChannel = new DMRLogicalChannel(getTrafficChannelRepeater(), getTrafficChannelTimeslot());
|
mDmrLsn = new DMRLsn(getTrafficLsn());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mDMRLogicalChannel;
|
return mDmrLsn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
return getChannel().getLSNArray();
|
return getChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,13 +157,7 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
getChannel().apply(timeslotFrequencies);
|
||||||
{
|
|
||||||
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
|
|
||||||
{
|
|
||||||
getChannel().setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
@@ -224,11 +223,11 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
|||||||
* Logical Slot Number(s) for channels contained in this message
|
* Logical Slot Number(s) for channels contained in this message
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
if(getMoveToChannel() instanceof DMRLogicalChannel)
|
if(getMoveToChannel() != null)
|
||||||
{
|
{
|
||||||
return ((DMRLogicalChannel)getMoveToChannel()).getLSNArray();
|
return getMoveToChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new int[0];
|
return new int[0];
|
||||||
@@ -241,17 +240,9 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
if(getMoveToChannel() instanceof DMRLogicalChannel)
|
if(getMoveToChannel() != null)
|
||||||
{
|
{
|
||||||
DMRLogicalChannel channel = (DMRLogicalChannel)getMoveToChannel();
|
getMoveToChannel().apply(timeslotFrequencies);
|
||||||
|
|
||||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
|
||||||
{
|
|
||||||
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
|
||||||
{
|
|
||||||
channel.setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ public class MoveTSCC extends CSBKMessage
|
|||||||
|
|
||||||
if(multiBlock != null)
|
if(multiBlock != null)
|
||||||
{
|
{
|
||||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
//Timeslot hard-coded to 1 since this is a control channel.
|
||||||
|
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,24 +23,20 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Tier III - Announcement Message - Adjacent/Neighbor Site Information
|
* DMR Tier III - Announcement Message - Adjacent/Neighbor Site Information
|
||||||
*/
|
*/
|
||||||
public class AdjacentSiteInformation extends Announcement implements ITimeslotFrequencyReceiver
|
public class AdjacentSiteInformation extends Announcement
|
||||||
{
|
{
|
||||||
private static final int NEIGHBOR_SYSTEM_IDENTITY_CODE_OFFSET = 21;
|
private static final int NEIGHBOR_SYSTEM_IDENTITY_CODE_OFFSET = 21;
|
||||||
private static final int NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG = 56;
|
private static final int NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG = 56;
|
||||||
@@ -88,8 +84,8 @@ public class AdjacentSiteInformation extends Announcement implements ITimeslotFr
|
|||||||
|
|
||||||
if(multiBlock != null)
|
if(multiBlock != null)
|
||||||
{
|
{
|
||||||
//Timeslot hard-coded to zero for control channel
|
//Timeslot hard-coded to one for control channel
|
||||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,39 +231,4 @@ public class AdjacentSiteInformation extends Announcement implements ITimeslotFr
|
|||||||
|
|
||||||
return mIdentifiers;
|
return mIdentifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Logical Slot Number(s) for channels contained in this message
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int[] getLogicalTimeslotNumbers()
|
|
||||||
{
|
|
||||||
if(getNeighborChannel() instanceof DMRLogicalChannel)
|
|
||||||
{
|
|
||||||
return ((DMRLogicalChannel)getNeighborChannel()).getLSNArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new int[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the timeslot frequency lookup information to channels contained in this message
|
|
||||||
* @param timeslotFrequencies that match the logical timeslots
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
|
||||||
{
|
|
||||||
if(getNeighborChannel() instanceof DMRLogicalChannel)
|
|
||||||
{
|
|
||||||
DMRLogicalChannel channel = (DMRLogicalChannel)getNeighborChannel();
|
|
||||||
|
|
||||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
|
||||||
{
|
|
||||||
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
|
||||||
{
|
|
||||||
channel.setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -56,7 +55,8 @@ public class AnnounceChannelFrequency extends Announcement
|
|||||||
|
|
||||||
if(multiBlock != null)
|
if(multiBlock != null)
|
||||||
{
|
{
|
||||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
//Timeslot hard-coded to one
|
||||||
|
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -87,8 +86,8 @@ public class AnnounceWithdrawTSCC extends Announcement
|
|||||||
|
|
||||||
if(multiBlock != null)
|
if(multiBlock != null)
|
||||||
{
|
{
|
||||||
//Control channel timeslot hard-coded to 0
|
//Control channel timeslot hard-coded to 1
|
||||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
@@ -33,7 +32,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -91,7 +89,8 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
|
|||||||
|
|
||||||
if(multiBlock != null)
|
if(multiBlock != null)
|
||||||
{
|
{
|
||||||
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
|
//Timeslot hard-coded to one for control channel
|
||||||
|
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,11 +239,11 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
|
|||||||
* Logical Slot Number(s) for channels contained in this message
|
* Logical Slot Number(s) for channels contained in this message
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
if(getChannel() instanceof DMRLogicalChannel)
|
if(getChannel() != null)
|
||||||
{
|
{
|
||||||
return ((DMRLogicalChannel)getChannel()).getLSNArray();
|
return getChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new int[0];
|
return new int[0];
|
||||||
@@ -257,17 +256,9 @@ public class VoteNowAdvice extends Announcement implements ITimeslotFrequencyRec
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
if(getChannel() instanceof DMRLogicalChannel)
|
if(getChannel() != null)
|
||||||
{
|
{
|
||||||
DMRLogicalChannel channel = (DMRLogicalChannel)getChannel();
|
getChannel().apply(timeslotFrequencies);
|
||||||
|
|
||||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
|
||||||
{
|
|
||||||
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
|
||||||
{
|
|
||||||
channel.setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public class BroadcastTalkgroupVoiceChannelGrant extends ChannelGrant
|
|||||||
|
|
||||||
sb.append(" BROADCAST TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
sb.append(" BROADCAST TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getDestinationTalkgroup());
|
sb.append(" TO:").append(getDestinationTalkgroup());
|
||||||
sb.append(" CHAN:").append(getChannel());
|
sb.append(" ").append(getChannel());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant;
|
|||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
@@ -32,7 +31,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,11 +157,11 @@ public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
|
|||||||
* Logical Slot Number(s) for channels contained in this message
|
* Logical Slot Number(s) for channels contained in this message
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
if(getChannel() instanceof DMRLogicalChannel)
|
if(getChannel() != null)
|
||||||
{
|
{
|
||||||
return ((DMRLogicalChannel)getChannel()).getLSNArray();
|
return getChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new int[0];
|
return new int[0];
|
||||||
@@ -176,17 +174,9 @@ public abstract class ChannelGrant extends CSBKMessage implements ITimeslotFrequ
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
if(getChannel() instanceof DMRLogicalChannel)
|
if(getChannel() != null)
|
||||||
{
|
{
|
||||||
DMRLogicalChannel channel = (DMRLogicalChannel)getChannel();
|
getChannel().apply(timeslotFrequencies);
|
||||||
|
|
||||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
|
||||||
{
|
|
||||||
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
|
||||||
{
|
|
||||||
channel.setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class DuplexPrivateDataChannelGrant extends ChannelGrant
|
|||||||
|
|
||||||
sb.append(" DUPLEX PRIVATE DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
sb.append(" DUPLEX PRIVATE DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getDestinationRadio());
|
sb.append(" TO:").append(getDestinationRadio());
|
||||||
sb.append(" CHAN:").append(getChannel());
|
sb.append(" ").append(getChannel());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ public class DuplexPrivateVoiceChannelGrant extends ChannelGrant
|
|||||||
|
|
||||||
sb.append(" DUPLEX PRIVATE VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
sb.append(" DUPLEX PRIVATE VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getDestinationRadio());
|
sb.append(" TO:").append(getDestinationRadio());
|
||||||
sb.append(" CHAN:").append(getChannel());
|
sb.append(" ").append(getChannel());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class PrivateDataChannelGrant extends ChannelGrant
|
|||||||
|
|
||||||
sb.append(" PRIVATE DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
sb.append(" PRIVATE DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getDestinationRadio());
|
sb.append(" TO:").append(getDestinationRadio());
|
||||||
sb.append(" CHAN:").append(getChannel());
|
sb.append(" ").append(getChannel());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public class PrivateVoiceChannelGrant extends ChannelGrant
|
|||||||
|
|
||||||
sb.append(" PRIVATE VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
sb.append(" PRIVATE VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getDestinationRadio());
|
sb.append(" TO:").append(getDestinationRadio());
|
||||||
sb.append(" CHAN:").append(getChannel());
|
sb.append(" ").append(getChannel());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ public class TalkgroupDataChannelGrant extends ChannelGrant
|
|||||||
|
|
||||||
sb.append(" TALKGROUP DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
sb.append(" TALKGROUP DATA CHANNEL GRANT FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getDestinationTalkgroup());
|
sb.append(" TO:").append(getDestinationTalkgroup());
|
||||||
sb.append(" CHAN:").append(getChannel());
|
sb.append(" ").append(getChannel());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public class TalkgroupVoiceChannelGrant extends ChannelGrant
|
|||||||
|
|
||||||
sb.append(" TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
sb.append(" TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getDestinationTalkgroup());
|
sb.append(" TO:").append(getDestinationTalkgroup());
|
||||||
sb.append(" CHAN:").append(getChannel());
|
sb.append(" ").append(getChannel());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.DefinedDataFormat;
|
import io.github.dsheirer.module.decode.dmr.message.type.DefinedDataFormat;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -39,6 +38,7 @@ public class DefinedShortDataHeader extends PacketSequenceHeader
|
|||||||
private static final int RESYNCHRONIZE_FLAG = 70;
|
private static final int RESYNCHRONIZE_FLAG = 70;
|
||||||
private static final int FULL_MESSAGE_FLAG = 71;
|
private static final int FULL_MESSAGE_FLAG = 71;
|
||||||
private static final int[] BIT_PADDING = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
private static final int[] BIT_PADDING = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
private static final int[] HEADER_CRC = new int[]{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95};
|
||||||
|
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
|||||||
@@ -17,18 +17,19 @@
|
|||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.message.data.header.hytera;
|
package io.github.dsheirer.module.decode.dmr.message.data.header;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessageFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hytera Proprietary Data Header
|
* PI/Encryption Header with Link Control
|
||||||
*/
|
*/
|
||||||
public class HyteraProprietaryDataHeader extends ProprietaryDataHeader
|
public class PiHeader extends HeaderMessage
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
@@ -40,23 +41,42 @@ public class HyteraProprietaryDataHeader extends ProprietaryDataHeader
|
|||||||
* @param timestamp message was received
|
* @param timestamp message was received
|
||||||
* @param timeslot for the DMR burst
|
* @param timeslot for the DMR burst
|
||||||
*/
|
*/
|
||||||
public HyteraProprietaryDataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
public PiHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
{
|
{
|
||||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the embedded link control message
|
||||||
|
*/
|
||||||
|
public LCMessage getLCMessage()
|
||||||
|
{
|
||||||
|
if(mLCMessage == null)
|
||||||
|
{
|
||||||
|
//Overrides default to ignore opcode and construct an encryption parameters message.
|
||||||
|
mLCMessage = LCMessageFactory.createFullEncryption(getMessage(), getTimestamp(), getTimeslot());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLCMessage;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
|
||||||
if(!isValid())
|
if(!isValid())
|
||||||
{
|
{
|
||||||
sb.append(" [CRC ERROR]");
|
sb.append(" [CRC ERROR]");
|
||||||
}
|
}
|
||||||
sb.append(" HYTERA PROPRIETARY DATA HEADER");
|
|
||||||
sb.append(" SAP:").append(getServiceAccessPoint());
|
sb.append(getSlotType());
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
|
||||||
|
if(hasRAS())
|
||||||
|
{
|
||||||
|
sb.append(" RAS:").append(getBPTCReservedBits()).append(" ");
|
||||||
|
}
|
||||||
|
sb.append(" ").append(getLCMessage());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -34,6 +34,11 @@ public class ProprietaryDataHeader extends DataHeader
|
|||||||
{
|
{
|
||||||
private static final int[] SERVICE_ACCESS_POINT = new int[]{0, 1, 2, 3};
|
private static final int[] SERVICE_ACCESS_POINT = new int[]{0, 1, 2, 3};
|
||||||
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
|
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
|
private static final int[] VENDOR_DATA = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||||
|
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||||
|
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
|
||||||
|
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
private static final int[] HEADER_CRC = new int[]{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
@@ -62,10 +67,20 @@ public class ProprietaryDataHeader extends DataHeader
|
|||||||
sb.append(" PROPRIETARY DATA HEADER");
|
sb.append(" PROPRIETARY DATA HEADER");
|
||||||
sb.append(" VENDOR:").append(getVendor());
|
sb.append(" VENDOR:").append(getVendor());
|
||||||
sb.append(" ").append(getServiceAccessPoint());
|
sb.append(" ").append(getServiceAccessPoint());
|
||||||
|
sb.append(" VENDOR DATA:").append(getVendorData());
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor defined data payload from this proprietary header.
|
||||||
|
* @return hex values.
|
||||||
|
*/
|
||||||
|
public String getVendorData()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(VENDOR_DATA, 16);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional packet prefix.
|
* Optional packet prefix.
|
||||||
* @return packet prefix fragment or null.
|
* @return packet prefix fragment or null.
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.data.header.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera Proprietary Data Header for SAP Short Data
|
||||||
|
*/
|
||||||
|
public class HyteraDataEncryptionHeader extends ProprietaryDataHeader
|
||||||
|
{
|
||||||
|
private static final int[] ALGORITHM = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] KEY = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] INITIALIZATION_VECTOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
|
||||||
|
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63};
|
||||||
|
private static final int[] UNKNOWN = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
*
|
||||||
|
* @param syncPattern either BASE_STATION_DATA or MOBILE_STATION_DATA
|
||||||
|
* @param message containing extracted 196-bit payload.
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this data message
|
||||||
|
* @param timestamp message was received
|
||||||
|
* @param timeslot for the DMR burst
|
||||||
|
*/
|
||||||
|
public HyteraDataEncryptionHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append(" [CRC ERROR]");
|
||||||
|
}
|
||||||
|
sb.append(" HYTERA DATA ENCRYPTION HEADER - SAP:").append(getServiceAccessPoint());
|
||||||
|
|
||||||
|
switch(getAlgorithm())
|
||||||
|
{
|
||||||
|
case NO_ENCRYPTION:
|
||||||
|
sb.append(" ALGORITHM:UNENCRYPTED");
|
||||||
|
break;
|
||||||
|
case UNKNOWN:
|
||||||
|
sb.append(" ALGORITHM:").append(getMessage().getInt(ALGORITHM));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(" ALGORITHM:").append(getAlgorithm());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getAlgorithm() != EncryptionAlgorithm.NO_ENCRYPTION)
|
||||||
|
{
|
||||||
|
sb.append(" KEY:").append(getKeyId());
|
||||||
|
sb.append(" IV:").append(getIV());
|
||||||
|
sb.append(" UNK:").append(getUnknown());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption algorithm.
|
||||||
|
* @return algorithm or UNKNOWN if the value is not known.
|
||||||
|
*/
|
||||||
|
public EncryptionAlgorithm getAlgorithm()
|
||||||
|
{
|
||||||
|
return EncryptionAlgorithm.fromValue(getMessage().getInt(ALGORITHM));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption key ID (1-255).
|
||||||
|
* @return key ID
|
||||||
|
*/
|
||||||
|
public int getKeyId()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hex string representation of the initialization vector
|
||||||
|
* @return IV in hex string.
|
||||||
|
*/
|
||||||
|
public String getIV()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(INITIALIZATION_VECTOR, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown field.
|
||||||
|
* @return hex string from the field contents.
|
||||||
|
*/
|
||||||
|
public String getUnknown()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -66,7 +66,8 @@ public class MNISProprietaryDataHeader extends ProprietaryDataHeader
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("MOTOROLA MNIS HEADER");
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" MOTOROLA MNIS HEADER");
|
||||||
if(getApplicationType() == ApplicationType.UNKNOWN)
|
if(getApplicationType() == ApplicationType.UNKNOWN)
|
||||||
{
|
{
|
||||||
sb.append(" APPLICATION TYPE:0x").append(Integer.toHexString(getApplicationTypeNumber()).toUpperCase());
|
sb.append(" APPLICATION TYPE:0x").append(Integer.toHexString(getApplicationTypeNumber()).toUpperCase());
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -31,10 +31,15 @@ import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
|||||||
/**
|
/**
|
||||||
* Motorola Proprietary Data Header
|
* Motorola Proprietary Data Header
|
||||||
*/
|
*/
|
||||||
public class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
|
public class MotorolaDataEncryptionHeader extends ProprietaryDataHeader
|
||||||
{
|
{
|
||||||
private static final int[] SERVICE_ACCESS_POINT = new int[]{0, 1, 2, 3};
|
private static final int[] SERVICE_ACCESS_POINT = new int[]{0, 1, 2, 3};
|
||||||
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
|
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
|
private static final int[] ALGORITHM = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] KEY_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] UNKNOWN = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] INITIALIZATION_VECTOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
|
||||||
|
62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
@@ -46,7 +51,7 @@ public class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
|
|||||||
* @param timestamp message was received
|
* @param timestamp message was received
|
||||||
* @param timeslot for the DMR burst
|
* @param timeslot for the DMR burst
|
||||||
*/
|
*/
|
||||||
public MotorolaProprietaryDataHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
public MotorolaDataEncryptionHeader(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
{
|
{
|
||||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
}
|
}
|
||||||
@@ -55,14 +60,57 @@ public class MotorolaProprietaryDataHeader extends ProprietaryDataHeader
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("MOTOROLA PROPRIETARY DATA HEADER");
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
sb.append(" ").append(getServiceAccessPoint());
|
sb.append(" MOTOROLA DATA ENCRYPTION HEADER");
|
||||||
|
sb.append(" SAP:").append(getServiceAccessPoint());
|
||||||
|
sb.append(" ALGORITHM?:").append(getAlgorithm());
|
||||||
|
sb.append(" KEY:").append(getKeyId());
|
||||||
|
sb.append(" IV:").append(getInitializationVector());
|
||||||
|
sb.append(" UNK:").append(getUnknown());
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown message field(s).
|
||||||
|
* @return hex value.
|
||||||
|
*/
|
||||||
|
public String getUnknown()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption key ID
|
||||||
|
* @return key ID
|
||||||
|
*/
|
||||||
|
public int getKeyId()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(KEY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption Algorithm
|
||||||
|
* @return algorithm ID
|
||||||
|
*/
|
||||||
|
public int getAlgorithm()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption initialization vector
|
||||||
|
*
|
||||||
|
* @return vector in hex
|
||||||
|
*/
|
||||||
|
public String getInitializationVector()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(INITIALIZATION_VECTOR, 8);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to lookup the vendor from a CSBK message
|
* Utility method to lookup the vendor from a CSBK message
|
||||||
|
*
|
||||||
* @param message containing CSBK bits
|
* @param message containing CSBK bits
|
||||||
* @return vendor
|
* @return vendor
|
||||||
*/
|
*/
|
||||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.edac.CRCDMR;
|
import io.github.dsheirer.edac.CRCDMR;
|
||||||
import io.github.dsheirer.edac.Checksum_5_DMR;
|
import io.github.dsheirer.edac.Checksum_5_DMR;
|
||||||
import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR;
|
import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
||||||
@@ -37,8 +38,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGr
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ActivityUpdateMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ActivityUpdateMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusControlChannel;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusControlChannel;
|
||||||
@@ -59,8 +60,20 @@ public class LCMessageFactory
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(LCMessageFactory.class);
|
private final static Logger mLog = LoggerFactory.getLogger(LCMessageFactory.class);
|
||||||
private static final ReedSolomon_12_9_4_DMR REED_SOLOMON_12_9_4_DMR = new ReedSolomon_12_9_4_DMR();
|
private static final ReedSolomon_12_9_4_DMR REED_SOLOMON_12_9_4_DMR = new ReedSolomon_12_9_4_DMR();
|
||||||
private static int TERMINATOR_LINK_CONTROL_CRC_MASK = 0x99;
|
private static final int TERMINATOR_LINK_CONTROL_CRC_MASK = 0x99;
|
||||||
private static int VOICE_LINK_CONTROL_CRC_MASK = 0x96;
|
private static final int VOICE_LINK_CONTROL_CRC_MASK = 0x96;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a full link control message specifically for the PI_HEADER message type.
|
||||||
|
* @param message bits
|
||||||
|
* @param timestamp of the original message
|
||||||
|
* @param timeslot of the original message
|
||||||
|
* @return encryption parameters link control.
|
||||||
|
*/
|
||||||
|
public static FullLCMessage createFullEncryption(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
return new EncryptionParameters(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a full link control message
|
* Creates a full link control message
|
||||||
@@ -112,8 +125,8 @@ public class LCMessageFactory
|
|||||||
case FULL_STANDARD_TERMINATOR_DATA:
|
case FULL_STANDARD_TERMINATOR_DATA:
|
||||||
flc = new TerminatorData(message, timestamp, timeslot);
|
flc = new TerminatorData(message, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
|
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
|
||||||
flc = new CapacityPlusGroupVoiceChannelUser(message, timestamp, timeslot);
|
flc = new MotorolaGroupVoiceChannelUser(message, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
|
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
|
||||||
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
|
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
|
||||||
@@ -121,7 +134,10 @@ public class LCMessageFactory
|
|||||||
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
|
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
|
||||||
flc = new CapacityPlusWideAreaVoiceChannelUser(message, timestamp, timeslot);
|
flc = new CapacityPlusWideAreaVoiceChannelUser(message, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
case FULL_ENCRYPTION_PARAMETERS:
|
||||||
|
flc = new EncryptionParameters(message, timestamp, timeslot);
|
||||||
|
valid = flc.isValid();
|
||||||
|
break;
|
||||||
case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER:
|
case FULL_HYTERA_GROUP_VOICE_CHANNEL_USER:
|
||||||
flc = new HyteraGroupVoiceChannelUser(message, timestamp, timeslot);
|
flc = new HyteraGroupVoiceChannelUser(message, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import java.util.TreeMap;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Full and Short Link Control Opcode enumeration
|
* Full and Short Link Control Opcode enumeration
|
||||||
*
|
|
||||||
* ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B.1 & B.2
|
* ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B.1 & B.2
|
||||||
* ETSI TS 102 361-4 DMR Trunking Protocol, Annex B.1 & B.2
|
* ETSI TS 102 361-4 DMR Trunking Protocol, Annex B.1 & B.2
|
||||||
*/
|
*/
|
||||||
@@ -44,10 +43,12 @@ public enum LCOpcode
|
|||||||
FULL_STANDARD_TERMINATOR_DATA(Vendor.STANDARD, true, 48, "TERMINATOR DATA"),
|
FULL_STANDARD_TERMINATOR_DATA(Vendor.STANDARD, true, 48, "TERMINATOR DATA"),
|
||||||
FULL_STANDARD_UNKNOWN(Vendor.STANDARD,true, -1, "FULL UNKNOWN"),
|
FULL_STANDARD_UNKNOWN(Vendor.STANDARD,true, -1, "FULL UNKNOWN"),
|
||||||
|
|
||||||
FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"),
|
FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"),
|
||||||
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
|
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
|
||||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||||
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
|
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
|
||||||
|
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||||
|
FULL_ENCRYPTION_PARAMETERS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 33, "ENCRYPTION PARAMETERS"),
|
||||||
//Cap+ opcodes from https://forums.radioreference.com/threads/understanding-capacity-plus-trunking-some-more.452566/
|
//Cap+ opcodes from https://forums.radioreference.com/threads/understanding-capacity-plus-trunking-some-more.452566/
|
||||||
//FLCO 0: Group Call Maintenance
|
//FLCO 0: Group Call Maintenance
|
||||||
//FLCO 3: Private Call Maintenance (TermLC)
|
//FLCO 3: Private Call Maintenance (TermLC)
|
||||||
@@ -79,10 +80,10 @@ public enum LCOpcode
|
|||||||
private static final EnumSet<LCOpcode> TALKER_ALIAS_OPCODES = EnumSet.of(FULL_STANDARD_TALKER_ALIAS_HEADER,
|
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);
|
FULL_STANDARD_TALKER_ALIAS_BLOCK_1, FULL_STANDARD_TALKER_ALIAS_BLOCK_2, FULL_STANDARD_TALKER_ALIAS_BLOCK_3);
|
||||||
|
|
||||||
private Vendor mVendor;
|
private final Vendor mVendor;
|
||||||
private boolean mFull;
|
private final boolean mFull;
|
||||||
private int mValue;
|
private final int mValue;
|
||||||
private String mLabel;
|
private final String mLabel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,7 +28,7 @@ import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
|||||||
*
|
*
|
||||||
* ETSI TS 102 361-2 7.1.1.2
|
* ETSI TS 102 361-2 7.1.1.2
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractVoiceChannelUser extends FullLCMessage
|
public abstract class AbstractVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider
|
||||||
{
|
{
|
||||||
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.data.lc.full;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.edac.CRCDMR;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption Parameters, carried by a PI_HEADER.
|
||||||
|
*
|
||||||
|
* Note: this message repurposes the OPCODE field to carry the encryption algorithm.
|
||||||
|
*/
|
||||||
|
public class EncryptionParameters extends FullLCMessage
|
||||||
|
{
|
||||||
|
private static final int[] KEY_ID = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] INITIALIZATION_VECTOR = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
|
||||||
|
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] DESTINATION_GROUP = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
|
||||||
|
71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
//CRC-CCITT with 0x9696 initial fill, bits: 80-95
|
||||||
|
|
||||||
|
private DMRTalkgroup mTalkgroup;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param message bits
|
||||||
|
* @param timestamp for the message
|
||||||
|
* @param timeslot of the message
|
||||||
|
*/
|
||||||
|
public EncryptionParameters(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
int bitErrors = CRCDMR.correctCCITT80(message, 0, 80, 0x9696);
|
||||||
|
setValid(bitErrors < 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the default method for opcode to return a static value, because the opcode field is repurposed to
|
||||||
|
* be the algorithm field in both Motorola and Hytera variants of this message, both carried by the Encryption
|
||||||
|
* Header message.
|
||||||
|
* @return full encryption parameters opcode.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public LCOpcode getOpcode()
|
||||||
|
{
|
||||||
|
return LCOpcode.FULL_ENCRYPTION_PARAMETERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isEncrypted())
|
||||||
|
{
|
||||||
|
sb.append(" *ENCRYPTED*");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isReservedBitSet())
|
||||||
|
{
|
||||||
|
sb.append(" *RESERVED-BIT*");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("FLC ENCRYPTION PARAMETERS");
|
||||||
|
sb.append(" VENDOR:").append(getVendor());
|
||||||
|
if(getAlgorithm() == EncryptionAlgorithm.UNKNOWN)
|
||||||
|
{
|
||||||
|
//List the algorithm (aka opcod) value for unknown algorithms
|
||||||
|
sb.append(" ALGORITHM:").append(getOpcodeValue());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(" ALGORITHM:").append(getAlgorithm());
|
||||||
|
}
|
||||||
|
sb.append(" KEY:").append(getKeyId());
|
||||||
|
sb.append(" IV:").append(getInitializationVector());
|
||||||
|
sb.append(" TALKGROUP:").append(getTalkgroup());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption parameter details
|
||||||
|
* @return details
|
||||||
|
*/
|
||||||
|
public String getDetails()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("ENCRYPTION ALGORITHM:").append(getAlgorithm());
|
||||||
|
sb.append(" KEY:").append(getKeyId());
|
||||||
|
sb.append(" IV:").append(getInitializationVector());
|
||||||
|
sb.append(" VENDOR:").append(getVendor());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption algorithm. Note: this message repurposes the OPCODE field to carry the algorithm ID.
|
||||||
|
* @return algorithm or UNKNOWN.
|
||||||
|
*/
|
||||||
|
public EncryptionAlgorithm getAlgorithm()
|
||||||
|
{
|
||||||
|
return EncryptionAlgorithm.fromValue(getOpcodeValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DMRTalkgroup getTalkgroup()
|
||||||
|
{
|
||||||
|
if(mTalkgroup == null)
|
||||||
|
{
|
||||||
|
mTalkgroup = new DMRTalkgroup(getMessage().getInt(DESTINATION_GROUP));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeyId()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(KEY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInitializationVector()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(INITIALIZATION_VECTOR, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getTalkgroup());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ public abstract class FullLCMessage extends LCMessage
|
|||||||
{
|
{
|
||||||
private static final int ENCRYPTION_FLAG = 0;
|
private static final int ENCRYPTION_FLAG = 0;
|
||||||
private static final int RESERVED = 1;
|
private static final int RESERVED = 1;
|
||||||
private static final int[] OPCODE = new int[]{2, 3, 4, 5, 6, 7};
|
protected static final int[] OPCODE = new int[]{2, 3, 4, 5, 6, 7};
|
||||||
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
|
private static final int[] VENDOR = new int[]{8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,13 +22,14 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera;
|
|||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hytera Full Link Control
|
* Hytera Full Link Control
|
||||||
*/
|
*/
|
||||||
public abstract class HyteraFullLC extends FullLCMessage
|
public abstract class HyteraFullLC extends FullLCMessage implements IServiceOptionsProvider
|
||||||
{
|
{
|
||||||
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
private static final int[] FREE_REPEATER = new int[]{24, 25, 26, 27};
|
private static final int[] FREE_REPEATER = new int[]{24, 25, 26, 27};
|
||||||
@@ -36,11 +37,12 @@ public abstract class HyteraFullLC extends FullLCMessage
|
|||||||
protected static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
protected static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
private static final int[] PRIORITY_CALL_HASHED_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
private static final int[] PRIORITY_CALL_HASHED_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
private static final int[] UNKNOWN_1 = new int[]{72, 73, 74, 75};
|
//FEC: 72-96
|
||||||
private static final int[] UNKNOWN_2 = new int[]{76, 77, 78, 79};
|
//TODO: Target and Source address fields may be full 24-bit values. How do we distinguish between Hytera repeater
|
||||||
|
//TODO: and a Hytera XPT repeater? Do the service options identify when it's XPT?
|
||||||
|
|
||||||
private ServiceOptions mServiceOptions;
|
private ServiceOptions mServiceOptions;
|
||||||
private RadioIdentifier mSourceRadio;
|
protected RadioIdentifier mSourceRadio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
@@ -109,24 +111,6 @@ public abstract class HyteraFullLC extends FullLCMessage
|
|||||||
return String.format("%02X", getMessage().getInt(PRIORITY_CALL_HASHED_ADDRESS)).toUpperCase();
|
return String.format("%02X", getMessage().getInt(PRIORITY_CALL_HASHED_ADDRESS)).toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown nibble one. This value remains consistent for the FLC between the Voice Header and the Terminator
|
|
||||||
* for calls. It does not seem to have correlation to the channel/repeater number
|
|
||||||
*/
|
|
||||||
public int getUnknownField1()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(UNKNOWN_1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown nibble two. For each talkgroup, this field shows one value for the Voice Header and the same value but
|
|
||||||
* bit-inverted in the Terminator.
|
|
||||||
*/
|
|
||||||
public int getUnknownField2()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(UNKNOWN_2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source radio address
|
* Source radio address
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,9 +21,10 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera;
|
|||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -32,6 +33,11 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class HyteraGroupVoiceChannelUser extends HyteraFullLC
|
public class HyteraGroupVoiceChannelUser extends HyteraFullLC
|
||||||
{
|
{
|
||||||
|
protected static final int[] TALKGROUP = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||||||
|
39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] SOURCE_RADIO = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
|
||||||
|
63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
|
|
||||||
private TalkgroupIdentifier mTalkgroup;
|
private TalkgroupIdentifier mTalkgroup;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
@@ -55,26 +61,11 @@ public class HyteraGroupVoiceChannelUser extends HyteraFullLC
|
|||||||
sb.append("[CRC-ERROR] ");
|
sb.append("[CRC-ERROR] ");
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append("FLC HYTERA XPT GROUP VOICE CHANNEL USER FM:");
|
sb.append("FLC HYTERA GROUP VOICE CHANNEL USER FM:");
|
||||||
sb.append(getSourceRadio());
|
sb.append(getSourceRadio());
|
||||||
sb.append(" TO:").append(getTalkgroup());
|
sb.append(" TO:").append(getTalkgroup());
|
||||||
if(isAllChannelsBusy())
|
|
||||||
{
|
|
||||||
sb.append(" ALL REPEATERS BUSY");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.append(" FREE REPEATER:").append(getFreeRepeater());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hasPriorityCall())
|
|
||||||
{
|
|
||||||
sb.append(" PRIORITY CALL FOR:").append(getPriorityCallHashedAddress());
|
|
||||||
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
|
|
||||||
}
|
|
||||||
sb.append(" ").append(getServiceOptions());
|
sb.append(" ").append(getServiceOptions());
|
||||||
sb.append(" UNK1:").append(getUnknownField1());
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
sb.append(" UNK2:").append(getUnknownField2());
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,12 +76,25 @@ public class HyteraGroupVoiceChannelUser extends HyteraFullLC
|
|||||||
{
|
{
|
||||||
if(mTalkgroup == null)
|
if(mTalkgroup == null)
|
||||||
{
|
{
|
||||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
|
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mTalkgroup;
|
return mTalkgroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio address
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceRadio()
|
||||||
|
{
|
||||||
|
if(mSourceRadio == null)
|
||||||
|
{
|
||||||
|
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadio;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Identifier> getIdentifiers()
|
public List<Identifier> getIdentifiers()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -73,8 +72,6 @@ public class HyteraTerminator extends HyteraFullLC
|
|||||||
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
|
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
|
||||||
}
|
}
|
||||||
sb.append(" ").append(getServiceOptions());
|
sb.append(" ").append(getServiceOptions());
|
||||||
sb.append(" UNK1:").append(getUnknownField1());
|
|
||||||
sb.append(" UNK2:").append(getUnknownField2());
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -72,8 +71,6 @@ public class HyteraUnitToUnitVoiceChannelUser extends HyteraFullLC
|
|||||||
sb.append(" PRIORITY CALL FOR:").append(getPriorityCallHashedAddress());
|
sb.append(" PRIORITY CALL FOR:").append(getPriorityCallHashedAddress());
|
||||||
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
|
sb.append(" ON REPEATER:").append(getPriorityCallRepeater());
|
||||||
}
|
}
|
||||||
sb.append(" UNK1:").append(getUnknownField1());
|
|
||||||
sb.append(" UNK2:").append(getUnknownField2());
|
|
||||||
sb.append(" ").append(getServiceOptions());
|
sb.append(" ").append(getServiceOptions());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class CapacityPlusEncryptedVoiceChannelUser extends CapacityPlusVoiceChan
|
|||||||
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
private static final int[] UNKNOWN_2 = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
private static final int[] UNKNOWN_2 = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
private static final int[] UNKNOWN_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
//Reed Solomon FEC: 72-95
|
||||||
|
|
||||||
private RadioIdentifier mRadio;
|
private RadioIdentifier mRadio;
|
||||||
private TalkgroupIdentifier mTalkgroup;
|
private TalkgroupIdentifier mTalkgroup;
|
||||||
@@ -94,10 +94,6 @@ public class CapacityPlusEncryptedVoiceChannelUser extends CapacityPlusVoiceChan
|
|||||||
{
|
{
|
||||||
return getMessage().getHex(UNKNOWN_2, 2);
|
return getMessage().getHex(UNKNOWN_2, 2);
|
||||||
}
|
}
|
||||||
public String getUnknown3()
|
|
||||||
{
|
|
||||||
return getMessage().getHex(UNKNOWN_3, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source radio address
|
* Source radio address
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* 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.data.lc.full.motorola;
|
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
|
||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Motorola Capacity Plus - Group Voice Channel User
|
|
||||||
*/
|
|
||||||
public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
|
|
||||||
{
|
|
||||||
private static final int[] CAPACITY_PLUS_GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
|
|
||||||
private static final int[] CONVENTIONAL_GROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
|
|
||||||
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
|
||||||
private static final int[] REST_CHANNEL = new int[]{51, 52, 53, 54};
|
|
||||||
private static final int[] REST_CHANNEL_TIMESLOT = new int[]{55};
|
|
||||||
private static final int[] CAPACITY_PLUS_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
|
||||||
private static final int[] CONVENTIONAL_SOURCE_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
|
|
||||||
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
|
||||||
private static final int[] UNKNOWN = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
|
||||||
|
|
||||||
private RadioIdentifier mRadio;
|
|
||||||
private TalkgroupIdentifier mTalkgroup;
|
|
||||||
private DMRLogicalChannel mRestChannel;
|
|
||||||
private List<Identifier> mIdentifiers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance.
|
|
||||||
*
|
|
||||||
* @param message for the link control payload
|
|
||||||
*/
|
|
||||||
public CapacityPlusGroupVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
|
||||||
{
|
|
||||||
super(message, timestamp, timeslot);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
if(!isValid())
|
|
||||||
{
|
|
||||||
sb.append("[CRC-ERROR] ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isEncrypted())
|
|
||||||
{
|
|
||||||
sb.append(" ENCRYPTED");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isReservedBitSet())
|
|
||||||
{
|
|
||||||
sb.append(" RESERVED-BIT");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(getServiceOptions().isCapacityPlus())
|
|
||||||
{
|
|
||||||
sb.append("FLC MOTOROLA CAP+ GROUP VOICE CHANNEL USER");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.append("FLC MOTOROLA CONV/IP SITE GROUP VOICE CHANNEL USER");
|
|
||||||
}
|
|
||||||
sb.append(" FM:").append(getRadio());
|
|
||||||
sb.append(" TO:").append(getTalkgroup());
|
|
||||||
|
|
||||||
if(hasRestChannel())
|
|
||||||
{
|
|
||||||
sb.append(" REST:");
|
|
||||||
sb.append(getRestChannel());
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(" ").append(getServiceOptions());
|
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown 8-bit field
|
|
||||||
*/
|
|
||||||
public String getUnknown()
|
|
||||||
{
|
|
||||||
return getMessage().getHex(UNKNOWN, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logical channel number (ie repeater number).
|
|
||||||
*/
|
|
||||||
public DMRLogicalChannel getRestChannel()
|
|
||||||
{
|
|
||||||
if(mRestChannel == null)
|
|
||||||
{
|
|
||||||
mRestChannel = new DMRLogicalChannel(getRestChannelRepeater(), getRestChannelTimeslot());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mRestChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest repeater number
|
|
||||||
*/
|
|
||||||
public int getRestChannelRepeater()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(REST_CHANNEL) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest timeslot
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public int getRestChannelTimeslot()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(REST_CHANNEL_TIMESLOT) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if this message has a rest channel indicated for the call
|
|
||||||
*/
|
|
||||||
public boolean hasRestChannel()
|
|
||||||
{
|
|
||||||
return getServiceOptions().isCapacityPlus() && getRestChannelRepeater() != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source radio address
|
|
||||||
*/
|
|
||||||
public RadioIdentifier getRadio()
|
|
||||||
{
|
|
||||||
if(mRadio == null)
|
|
||||||
{
|
|
||||||
if(getServiceOptions().isCapacityPlus())
|
|
||||||
{
|
|
||||||
mRadio = DMRRadio.createFrom(getMessage().getInt(CAPACITY_PLUS_SOURCE_ADDRESS));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mRadio = DMRRadio.createFrom(getMessage().getInt(CONVENTIONAL_SOURCE_ADDRESS));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mRadio;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Talkgroup address
|
|
||||||
*/
|
|
||||||
public TalkgroupIdentifier getTalkgroup()
|
|
||||||
{
|
|
||||||
if(mTalkgroup == null)
|
|
||||||
{
|
|
||||||
if(getServiceOptions().isCapacityPlus())
|
|
||||||
{
|
|
||||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CAPACITY_PLUS_GROUP_ADDRESS));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CONVENTIONAL_GROUP_ADDRESS));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mTalkgroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Identifier> getIdentifiers()
|
|
||||||
{
|
|
||||||
if(mIdentifiers == null)
|
|
||||||
{
|
|
||||||
mIdentifiers = new ArrayList<>();
|
|
||||||
mIdentifiers.add(getTalkgroup());
|
|
||||||
mIdentifiers.add(getRadio());
|
|
||||||
|
|
||||||
if(hasRestChannel())
|
|
||||||
{
|
|
||||||
mIdentifiers.add(getRestChannel());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mIdentifiers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int[] getLogicalTimeslotNumbers()
|
|
||||||
{
|
|
||||||
return getRestChannel().getLSNArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the LSN to frequency map to the rest channel.
|
|
||||||
*
|
|
||||||
* @param timeslotFrequencies that match the logical timeslots
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
|
||||||
{
|
|
||||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
|
||||||
{
|
|
||||||
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
|
||||||
{
|
|
||||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,15 +20,17 @@
|
|||||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.CapacityPlusServiceOptions;
|
import io.github.dsheirer.module.decode.dmr.message.type.CapacityPlusServiceOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any Capacity Plus Voice Channel User link control message that contains vendor-specific service options.
|
* Any Capacity Plus Voice Channel User link control message that contains vendor-specific service options.
|
||||||
*/
|
*/
|
||||||
public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage
|
public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage implements IServiceOptionsProvider
|
||||||
{
|
{
|
||||||
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
//Reed Solomon FEC: 72-95
|
||||||
|
|
||||||
private CapacityPlusServiceOptions mServiceOptions;
|
private CapacityPlusServiceOptions mServiceOptions;
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
@@ -32,21 +32,23 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Motorola Capacity Plus - Wide Area (Multi-Site) Voice Channel User
|
* Motorola Capacity Plus - Wide Area (Linked or Multi-Site) Voice Channel User
|
||||||
|
*
|
||||||
|
* Note: in Linked Capacity Plus talkgroups range 1-255 and radio IDs range 1-65535
|
||||||
*/
|
*/
|
||||||
public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
|
public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
//Bits 16-23: Service Options
|
||||||
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
|
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
//private static final int[] REST_REPEATER = new int[]{51, 52, 53, 54};
|
private static final int[] GROUP_ID = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
//private static final int[] REST_TIMESLOT = new int[]{55};
|
private static final int[] UNUSED = new int[]{48, 49, 50};
|
||||||
private static final int[] REST_LSN = new int[]{52, 53, 54, 55};
|
private static final int[] REST_LSN = new int[]{51, 52, 53, 54, 55};
|
||||||
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
private static final int[] RADIO_ID = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
private static final int[] UNKNOWN_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
//Reed Solomon FEC: 72-95
|
||||||
|
|
||||||
private RadioIdentifier mRadio;
|
private RadioIdentifier mRadio;
|
||||||
private TalkgroupIdentifier mTalkgroup;
|
private TalkgroupIdentifier mTalkgroup;
|
||||||
private DMRLogicalChannel mRestChannel;
|
private DmrRestLsn mRestChannel;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,18 +84,12 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
|||||||
sb.append("FLC MOTOROLA CAP+ WIDE-AREA VOICE CHANNEL USER FM:");
|
sb.append("FLC MOTOROLA CAP+ WIDE-AREA VOICE CHANNEL USER FM:");
|
||||||
sb.append(getRadio());
|
sb.append(getRadio());
|
||||||
sb.append(" TO:").append(getTalkgroup());
|
sb.append(" TO:").append(getTalkgroup());
|
||||||
sb.append(" REST:");
|
|
||||||
if(hasRestChannel())
|
if(hasRestChannel())
|
||||||
{
|
{
|
||||||
sb.append(getRestChannel());
|
sb.append(" ").append(getRestChannel());
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.append("--");
|
|
||||||
}
|
|
||||||
sb.append(" UNK1:").append(getUnknown1());
|
|
||||||
sb.append(" UNK2:").append(getUnknown2());
|
|
||||||
sb.append(" ").append(getServiceOptions());
|
sb.append(" ").append(getServiceOptions());
|
||||||
|
sb.append(" UNK1:").append(getUnknown1());
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -101,27 +97,19 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
|||||||
/**
|
/**
|
||||||
* Unknown1 8-bit field
|
* Unknown1 8-bit field
|
||||||
*/
|
*/
|
||||||
public int getUnknown1()
|
public String getUnknown1()
|
||||||
{
|
{
|
||||||
return getMessage().getInt(UNKNOWN_1);
|
return getMessage().getHex(UNKNOWN_1, 4);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown2 8-bit field
|
|
||||||
*/
|
|
||||||
public int getUnknown2()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(UNKNOWN_3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical channel number (ie repeater number).
|
* Logical channel number (ie repeater number).
|
||||||
*/
|
*/
|
||||||
public DMRLogicalChannel getRestChannel()
|
public DmrRestLsn getRestChannel()
|
||||||
{
|
{
|
||||||
if(mRestChannel == null)
|
if(mRestChannel == null)
|
||||||
{
|
{
|
||||||
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
|
mRestChannel = new DmrRestLsn(getRestLSN());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mRestChannel;
|
return mRestChannel;
|
||||||
@@ -136,25 +124,12 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
|||||||
return getMessage().getInt(REST_LSN);
|
return getMessage().getInt(REST_LSN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest channel timeslot
|
|
||||||
*/
|
|
||||||
public int getRestTimeslot()
|
|
||||||
{
|
|
||||||
return (getRestLSN() % 2 == 0) ? 2 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest channel repeater number
|
|
||||||
*/
|
|
||||||
public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this message has a reset channel defined.
|
* Indicates if this message has a reset channel defined.
|
||||||
*/
|
*/
|
||||||
public boolean hasRestChannel()
|
public boolean hasRestChannel()
|
||||||
{
|
{
|
||||||
return getRestRepeater() != 0;
|
return getRestLSN() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,7 +139,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
|||||||
{
|
{
|
||||||
if(mRadio == null)
|
if(mRadio == null)
|
||||||
{
|
{
|
||||||
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
|
mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mRadio;
|
return mRadio;
|
||||||
@@ -177,7 +152,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
|||||||
{
|
{
|
||||||
if(mTalkgroup == null)
|
if(mTalkgroup == null)
|
||||||
{
|
{
|
||||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
|
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mTalkgroup;
|
return mTalkgroup;
|
||||||
@@ -201,9 +176,9 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
|||||||
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
|
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
return getRestChannel().getLSNArray();
|
return getRestChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,12 +189,6 @@ public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChann
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
|
getRestChannel().apply(timeslotFrequencies);
|
||||||
{
|
|
||||||
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
|
||||||
{
|
|
||||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.data.lc.full.motorola;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motorola Capacity Plus - Group Voice Channel User
|
||||||
|
*/
|
||||||
|
public class MotorolaGroupVoiceChannelUser extends CapacityPlusVoiceChannelUser
|
||||||
|
{
|
||||||
|
private static final int[] GROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
|
||||||
|
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] SOURCE_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
|
||||||
|
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
|
//Reed Solomon FEC: 72-95
|
||||||
|
|
||||||
|
private RadioIdentifier mRadio;
|
||||||
|
private TalkgroupIdentifier mTalkgroup;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
*
|
||||||
|
* @param message for the link control payload
|
||||||
|
*/
|
||||||
|
public MotorolaGroupVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(message, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isEncrypted())
|
||||||
|
{
|
||||||
|
sb.append(" ENCRYPTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isReservedBitSet())
|
||||||
|
{
|
||||||
|
sb.append(" RESERVED-BIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("FLC MOTOROLA GROUP VOICE CHANNEL USER");
|
||||||
|
sb.append(" FM:").append(getRadio());
|
||||||
|
sb.append(" TO:").append(getTalkgroup());
|
||||||
|
sb.append(" ").append(getServiceOptions());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio address
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getRadio()
|
||||||
|
{
|
||||||
|
if(mRadio == null)
|
||||||
|
{
|
||||||
|
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talkgroup address
|
||||||
|
*/
|
||||||
|
public TalkgroupIdentifier getTalkgroup()
|
||||||
|
{
|
||||||
|
if(mTalkgroup == null)
|
||||||
|
{
|
||||||
|
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTalkgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getTalkgroup());
|
||||||
|
mIdentifiers.add(getRadio());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,7 +22,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
|
|||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.Activity;
|
import io.github.dsheirer.module.decode.dmr.message.type.Activity;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -55,13 +54,18 @@ public class ActivityUpdateMessage extends ShortLCMessage
|
|||||||
{
|
{
|
||||||
sb.append("[CRC ERROR] ");
|
sb.append("[CRC ERROR] ");
|
||||||
}
|
}
|
||||||
|
sb.append("SLC TS1:");
|
||||||
sb.append("SLC ACTIVITY UPDATE TS1 [");
|
|
||||||
sb.append(getHashAddressTS1()).append("] ");
|
|
||||||
sb.append(getActivityTS1());
|
sb.append(getActivityTS1());
|
||||||
sb.append(" / TS2 [");
|
if(getActivityTS1() != Activity.IDLE)
|
||||||
sb.append(getHashAddressTS2()).append("] ");
|
{
|
||||||
|
sb.append(" [").append(getHashAddressTS1()).append("]");
|
||||||
|
}
|
||||||
|
sb.append(" TS2:");
|
||||||
sb.append(getActivityTS2());
|
sb.append(getActivityTS2());
|
||||||
|
if(getActivityTS2() != Activity.IDLE)
|
||||||
|
{
|
||||||
|
sb.append(" [").append(getHashAddressTS2()).append("]");
|
||||||
|
}
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
|
|||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
|
||||||
@@ -34,13 +34,11 @@ import java.util.List;
|
|||||||
public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslotFrequencyReceiver
|
public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
private static final int[] UNKNOWN = new int[]{12, 13, 14};
|
private static final int[] UNKNOWN = new int[]{12, 13, 14};
|
||||||
//private static final int[] REST_REPEATER = new int[]{15, 16, 17, 18};
|
|
||||||
private static final int[] REST_LSN = new int[]{15, 16, 17, 18, 19};
|
private static final int[] REST_LSN = new int[]{15, 16, 17, 18, 19};
|
||||||
//private static final int[] REST_TIMESLOT = new int[]{19};
|
|
||||||
private static final int[] SITE = new int[]{20, 21, 22, 23, 24};
|
private static final int[] SITE = new int[]{20, 21, 22, 23, 24};
|
||||||
private static final int[] UNKNOWN_2 = new int[]{25, 26, 27};
|
private static final int[] UNKNOWN_2 = new int[]{25, 26, 27};
|
||||||
|
|
||||||
private DMRLogicalChannel mRestChannel;
|
private DmrRestLsn mRestChannel;
|
||||||
private DMRSite mSite;
|
private DMRSite mSite;
|
||||||
private List<Identifier> mIdentifiers;
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
@@ -63,7 +61,7 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
|||||||
sb.append("[CRC ERROR] ");
|
sb.append("[CRC ERROR] ");
|
||||||
}
|
}
|
||||||
sb.append("SLC MOTOROLA CAP+ SITE:").append(getSite());
|
sb.append("SLC MOTOROLA CAP+ SITE:").append(getSite());
|
||||||
sb.append(" REST:").append(getRestChannel());
|
sb.append(" ").append(getRestChannel());
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
@@ -84,11 +82,11 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
|||||||
/**
|
/**
|
||||||
* Rest Channel Number
|
* Rest Channel Number
|
||||||
*/
|
*/
|
||||||
public DMRLogicalChannel getRestChannel()
|
public DmrRestLsn getRestChannel()
|
||||||
{
|
{
|
||||||
if(mRestChannel == null)
|
if(mRestChannel == null)
|
||||||
{
|
{
|
||||||
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
|
mRestChannel = new DmrRestLsn(getRestLSN());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mRestChannel;
|
return mRestChannel;
|
||||||
@@ -103,26 +101,13 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
|||||||
return getMessage().getInt(REST_LSN);
|
return getMessage().getInt(REST_LSN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest repeater
|
|
||||||
*/
|
|
||||||
public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rest timeslot
|
|
||||||
*/
|
|
||||||
public int getRestTimeslot()
|
|
||||||
{
|
|
||||||
return (getRestLSN() % 2 == 0) ? 2 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
|
* Exposes the rest channel logical slot number so that a LSN to frequency map can be applied to this message.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int[] getLogicalTimeslotNumbers()
|
public int[] getLogicalChannelNumbers()
|
||||||
{
|
{
|
||||||
return getRestChannel().getLSNArray();
|
return getRestChannel().getLogicalChannelNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,13 +117,7 @@ public class CapacityPlusRestChannel extends ShortLCMessage implements ITimeslot
|
|||||||
@Override
|
@Override
|
||||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
{
|
{
|
||||||
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
getRestChannel().apply(timeslotFrequencies);
|
||||||
{
|
|
||||||
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
|
|
||||||
{
|
|
||||||
getRestChannel().setTimeslotFrequency(timeslotFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,7 +21,6 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc.shorty;
|
|||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -48,8 +47,7 @@ public class NullMessage extends ShortLCMessage
|
|||||||
{
|
{
|
||||||
sb.append("[CRC ERROR] ");
|
sb.append("[CRC ERROR] ");
|
||||||
}
|
}
|
||||||
sb.append("SLC IDLE/NULL MESSAGE");
|
sb.append("SLC TS1:IDLE TS2:IDLE");
|
||||||
sb.append(" MSG:").append(getMessage().toHexString());
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,7 +23,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||||
import io.github.dsheirer.module.decode.ip.IPacket;
|
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -75,6 +74,10 @@ public class DMRPacketMessage extends DMRMessage
|
|||||||
sb.append("CC:").append(getPacketSequence().getPacketSequenceHeader().getSlotType().getColorCode());
|
sb.append("CC:").append(getPacketSequence().getPacketSequenceHeader().getSlotType().getColorCode());
|
||||||
sb.append(" FM:").append(getPacketSequence().getPacketSequenceHeader().getSourceLLID());
|
sb.append(" FM:").append(getPacketSequence().getPacketSequenceHeader().getSourceLLID());
|
||||||
sb.append(" TO:").append(getPacketSequence().getPacketSequenceHeader().getDestinationLLID());
|
sb.append(" TO:").append(getPacketSequence().getPacketSequenceHeader().getDestinationLLID());
|
||||||
|
if(getPacketSequence().isEncrypted())
|
||||||
|
{
|
||||||
|
sb.append(" ENCRYPTED");
|
||||||
|
}
|
||||||
sb.append(" ").append(getPacket().toString());
|
sb.append(" ").append(getPacket().toString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -32,12 +35,12 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class PacketSequence
|
public class PacketSequence
|
||||||
{
|
{
|
||||||
private int mTimeslot;
|
private final int mTimeslot;
|
||||||
private PacketSequenceHeader mPacketSequenceHeader;
|
private PacketSequenceHeader mPacketSequenceHeader;
|
||||||
private UDTHeader mUDTHeader;
|
private UDTHeader mUDTHeader;
|
||||||
private ProprietaryDataHeader mProprietaryDataHeader;
|
private ProprietaryDataHeader mProprietaryDataHeader;
|
||||||
private List<Preamble> mPreambles = new ArrayList<>();
|
private final List<Preamble> mPreambles = new ArrayList<>();
|
||||||
private List<DataBlock> mDataBlocks = new ArrayList<>();
|
private final List<DataBlock> mDataBlocks = new ArrayList<>();
|
||||||
|
|
||||||
public PacketSequence(int timeslot)
|
public PacketSequence(int timeslot)
|
||||||
{
|
{
|
||||||
@@ -89,6 +92,27 @@ public class PacketSequence
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this sequence contains a Motorola data encryption header.
|
||||||
|
* @return true if so
|
||||||
|
*/
|
||||||
|
public boolean isEncrypted()
|
||||||
|
{
|
||||||
|
if(hasProprietaryDataHeader())
|
||||||
|
{
|
||||||
|
if(getProprietaryDataHeader() instanceof MotorolaDataEncryptionHeader)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(getProprietaryDataHeader() instanceof HyteraDataEncryptionHeader hdeh)
|
||||||
|
{
|
||||||
|
return hdeh.getAlgorithm() != EncryptionAlgorithm.NO_ENCRYPTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public int getTimeslot()
|
public int getTimeslot()
|
||||||
{
|
{
|
||||||
return mTimeslot;
|
return mTimeslot;
|
||||||
|
|||||||
@@ -25,14 +25,19 @@ import io.github.dsheirer.message.IMessage;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
|
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.DataPacketFormat;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
|
||||||
|
import io.github.dsheirer.module.decode.ip.DefinedShortDataPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
|
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraTokenHeader;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header;
|
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Header;
|
||||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
||||||
@@ -164,24 +169,46 @@ public class PacketSequenceMessageFactory
|
|||||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(secondaryHeader instanceof HyteraProprietaryDataHeader)
|
else if(secondaryHeader instanceof HyteraDataEncryptionHeader hdeh)
|
||||||
{
|
{
|
||||||
HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
|
ServiceAccessPoint sap = hdeh.getServiceAccessPoint();
|
||||||
|
|
||||||
if(hyteraTokenHeader.isSMSMessage())
|
switch(sap)
|
||||||
{
|
{
|
||||||
return new DMRPacketMessage(packetSequence, new HyteraSmsPacket(hyteraTokenHeader), packet,
|
case SHORT_DATA:
|
||||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
return new DMRPacketMessage(packetSequence, new HyteraShortDataPacket(packetSequence, packet), packet,
|
||||||
}
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
else
|
case PROPRIETARY_DATA:
|
||||||
{
|
HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
|
||||||
return new DMRPacketMessage(packetSequence, new HyteraUnknownPacket(hyteraTokenHeader), packet,
|
if(hyteraTokenHeader.isSMSMessage())
|
||||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
{
|
||||||
|
return new DMRPacketMessage(packetSequence, new HyteraSmsPacket(hyteraTokenHeader), packet,
|
||||||
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new DMRPacketMessage(packetSequence, new HyteraUnknownPacket(hyteraTokenHeader), packet,
|
||||||
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
|
||||||
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(secondaryHeader instanceof MotorolaDataEncryptionHeader &&
|
||||||
|
packetSequence.getPacketSequenceHeader().getDataPacketFormat() == DataPacketFormat.DEFINED_SHORT_DATA)
|
||||||
|
{
|
||||||
|
return createDefinedShortData(packetSequence, packet);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.info("Unknown Proprietary Packet Header Type - creating unknown packet.");
|
if(packetSequence.getProprietaryDataHeader() != null)
|
||||||
|
{
|
||||||
|
mLog.info("Unknown Proprietary Packet Header Type - creating unknown packet. Data Packet Format: " +
|
||||||
|
packetSequence.getPacketSequenceHeader().getDataPacketFormat() + " Proprietary Header: " +
|
||||||
|
packetSequence.getProprietaryDataHeader().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
|
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
|
||||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
}
|
}
|
||||||
@@ -217,9 +244,19 @@ public class PacketSequenceMessageFactory
|
|||||||
*/
|
*/
|
||||||
public static IMessage createDefinedShortData(PacketSequence packetSequence, CorrectedBinaryMessage packet)
|
public static IMessage createDefinedShortData(PacketSequence packetSequence, CorrectedBinaryMessage packet)
|
||||||
{
|
{
|
||||||
mLog.info("Unknown Short Data Packet Header Type - creating unknown packet.");
|
//Test to see if this is Hytera Radio Registration Service (RRS)
|
||||||
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
|
HyteraTokenHeader hyteraTokenHeader = new HyteraTokenHeader(packet);
|
||||||
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
|
||||||
|
if(hyteraTokenHeader.isRRSMessage())
|
||||||
|
{
|
||||||
|
return new DMRPacketMessage(packetSequence, new HyteraRrsPacket(hyteraTokenHeader), packet,
|
||||||
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new DMRPacketMessage(packetSequence, new DefinedShortDataPacket(packet, 0), packet,
|
||||||
|
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,7 +272,7 @@ public class PacketSequenceMessageFactory
|
|||||||
|
|
||||||
if(sequence.hasProprietaryDataHeader())
|
if(sequence.hasProprietaryDataHeader())
|
||||||
{
|
{
|
||||||
CorrectedBinaryMessage prefix = ((ProprietaryDataHeader)sequence.getProprietaryDataHeader()).getPacketPrefix();
|
CorrectedBinaryMessage prefix = sequence.getProprietaryDataHeader().getPacketPrefix();
|
||||||
|
|
||||||
if(prefix != null)
|
if(prefix != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public class CapacityPlusServiceOptions extends ServiceOptions
|
|||||||
|
|
||||||
if(isInterruptible())
|
if(isInterruptible())
|
||||||
{
|
{
|
||||||
flags.add("TXI CALL");
|
flags.add("INTERRUPTIBLE CALL"); //Indicates another user can interrupt this call
|
||||||
}
|
}
|
||||||
|
|
||||||
if(getPriority() > 0)
|
if(getPriority() > 0)
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of encryption algorithms
|
||||||
|
*/
|
||||||
|
public enum EncryptionAlgorithm
|
||||||
|
{
|
||||||
|
NO_ENCRYPTION(0x00, "NO ENCRYPTION"),
|
||||||
|
HYTERA_BASIC_PRIVACY(0x01, "HYTERA BP"),
|
||||||
|
HYTERA_ENHANCED_PRIVACY(0x02, "HYTERA RC4/EP"),
|
||||||
|
DMRA_RC4(0x21, "DMRA RC4/EP"),
|
||||||
|
DMRA_AES128(0x24, "DMRA AES128"),
|
||||||
|
DMRA_AES256(0x25, "DMRA AES256" ),
|
||||||
|
HYTERA_ENHANCED_PRIVACY_2(0x26, "HYTERA RC4/EP"),
|
||||||
|
UNKNOWN(-1, "UNKNOWN");
|
||||||
|
|
||||||
|
private final int mValue;
|
||||||
|
private final String mLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param value for the algorithm
|
||||||
|
* @param label to display
|
||||||
|
*/
|
||||||
|
EncryptionAlgorithm(int value, String label)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
mLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric value for the algorithm
|
||||||
|
* @return value.
|
||||||
|
*/
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to lookup the encryption from the value.
|
||||||
|
* @param value to lookup
|
||||||
|
* @return matching entry or UNKNOWN
|
||||||
|
*/
|
||||||
|
public static EncryptionAlgorithm fromValue(int value)
|
||||||
|
{
|
||||||
|
for(EncryptionAlgorithm algorithm: EncryptionAlgorithm.values())
|
||||||
|
{
|
||||||
|
if(algorithm.getValue() == value)
|
||||||
|
{
|
||||||
|
return algorithm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,18 +21,16 @@ package io.github.dsheirer.module.decode.dmr.message.voice;
|
|||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
|
import io.github.dsheirer.module.decode.dmr.message.type.LCSS;
|
||||||
|
import java.util.HashSet;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Voice Frame B-F Embedded Signalling Chunk
|
* DMR Voice Frame B-F Embedded Signalling Chunk
|
||||||
*/
|
*/
|
||||||
public class EMB
|
public class EMB
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(EMB.class);
|
private final static Logger mLog = LoggerFactory.getLogger(EMB.class);
|
||||||
public static final int[] CRC_CHECKSUMS = new int[]{0x02F, 0x11E, 0x1B7, 0x1E2, 0x1C9, 0x0E5, 0x073};
|
|
||||||
private static final int[] VALID_WORDS = new int[]{
|
private static final int[] VALID_WORDS = new int[]{
|
||||||
0x0000, 0x0273, 0x04E5, 0x0696, 0x09C9, 0x0BBA, 0x0D2C, 0x0F5F, 0x11E2, 0x1391, 0x1507, 0x1774, 0x182B, 0x1A58,
|
0x0000, 0x0273, 0x04E5, 0x0696, 0x09C9, 0x0BBA, 0x0D2C, 0x0F5F, 0x11E2, 0x1391, 0x1507, 0x1774, 0x182B, 0x1A58,
|
||||||
0x1CCE, 0x1EBD, 0x21B7, 0x23C4, 0x2552, 0x2721, 0x287E, 0x2A0D, 0x2C9B, 0x2EE8, 0x3055, 0x3226, 0x34B0, 0x36C3,
|
0x1CCE, 0x1EBD, 0x21B7, 0x23C4, 0x2552, 0x2721, 0x287E, 0x2A0D, 0x2C9B, 0x2EE8, 0x3055, 0x3226, 0x34B0, 0x36C3,
|
||||||
|
|||||||
@@ -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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.bits.BinaryMessage;
|
|||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Voice Frames B - F
|
* DMR Voice Frames B - F
|
||||||
@@ -35,6 +36,7 @@ public class VoiceEMBMessage extends VoiceMessage
|
|||||||
private static final int PAYLOAD_END = 172;
|
private static final int PAYLOAD_END = 172;
|
||||||
|
|
||||||
private EMB mEMB;
|
private EMB mEMB;
|
||||||
|
private EmbeddedParameters mEmbeddedParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
|
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
|
||||||
@@ -61,7 +63,11 @@ public class VoiceEMBMessage extends VoiceMessage
|
|||||||
|
|
||||||
sb.append(getSyncPattern().toString());
|
sb.append(getSyncPattern().toString());
|
||||||
|
|
||||||
if(getEMB().isValid() && getEMB().isEncrypted())
|
if(hasEmbeddedParameters())
|
||||||
|
{
|
||||||
|
sb.append(" ").append(getEmbeddedParameters());
|
||||||
|
}
|
||||||
|
else if(getEMB().isValid() && getEMB().isEncrypted())
|
||||||
{
|
{
|
||||||
sb.append(" ENCRYPTED");
|
sb.append(" ENCRYPTED");
|
||||||
}
|
}
|
||||||
@@ -97,4 +103,34 @@ public class VoiceEMBMessage extends VoiceMessage
|
|||||||
{
|
{
|
||||||
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
|
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional embedded parameters for this voice super-frame.
|
||||||
|
* @return encryption parameters or null.
|
||||||
|
*/
|
||||||
|
public EmbeddedParameters getEmbeddedParameters()
|
||||||
|
{
|
||||||
|
return mEmbeddedParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the embedded parameters for this voice message that apply to the entire voice super-frame.
|
||||||
|
*
|
||||||
|
* These parameters are normally extracted by an external process and applied to voice frame F and the parameters
|
||||||
|
* apply to the entire voice super-frame.
|
||||||
|
* @param embeddedParameters to set
|
||||||
|
*/
|
||||||
|
public void setEmbeddedParameters(EmbeddedParameters embeddedParameters)
|
||||||
|
{
|
||||||
|
mEmbeddedParameters = embeddedParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this voice message contains embedded parameters.
|
||||||
|
* @return true if it contains.
|
||||||
|
*/
|
||||||
|
public boolean hasEmbeddedParameters()
|
||||||
|
{
|
||||||
|
return mEmbeddedParameters != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.voice;
|
package io.github.dsheirer.module.decode.dmr.message.voice;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
@@ -5,7 +24,6 @@ import io.github.dsheirer.identifier.Identifier;
|
|||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -15,6 +33,14 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public abstract class VoiceMessage extends DMRBurst
|
public abstract class VoiceMessage extends DMRBurst
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Initialization vector (IV) fragments can be embedded into the AMBE voice frame for late entry to encrypted calls
|
||||||
|
* using bits 71, 67, 63, and 59 from the interleaved (ie transmitted order) voice frame.
|
||||||
|
*/
|
||||||
|
public static int[] FRAME_1_IV_FRAGMENT = new int[]{95, 91, 87, 83};
|
||||||
|
public static int[] FRAME_2_IV_FRAGMENT = new int[]{215, 211, 207, 203};
|
||||||
|
public static int[] FRAME_3_IV_FRAGMENT = new int[]{287, 283, 279, 275};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
|
* DMR message frame. This message is comprised of a 24-bit prefix and a 264-bit message frame. Outbound base
|
||||||
* station frames transmit a Common Announcement Channel (CACH) in the 24-bit prefix, whereas Mobile inbound frames
|
* station frames transmit a Common Announcement Channel (CACH) in the 24-bit prefix, whereas Mobile inbound frames
|
||||||
@@ -65,6 +91,24 @@ public abstract class VoiceMessage extends DMRBurst
|
|||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the four low-order bits from the deinterleaved C3 vector of each of the three AMBE frames that carry
|
||||||
|
* fragments of the initialization vector (IV) for encrypted calls. Since we don't have access to the deinterleaved
|
||||||
|
* voice frame here, we perform delinterleaving of the 4-bit nibble using the FRAME_x_IV_FRAGMENT constants.
|
||||||
|
* <p>
|
||||||
|
* See patent: https://patents.google.com/patent/EP2347540B1/en
|
||||||
|
*
|
||||||
|
* @return a three-byte array with the low-order four bits from each frame's C3 vector stored in the low nibble.
|
||||||
|
*/
|
||||||
|
public byte[] getIvFragments()
|
||||||
|
{
|
||||||
|
byte[] fragments = new byte[3];
|
||||||
|
fragments[0] = (byte)getMessage().getInt(FRAME_1_IV_FRAGMENT);
|
||||||
|
fragments[1] = (byte)getMessage().getInt(FRAME_2_IV_FRAGMENT);
|
||||||
|
fragments[2] = (byte)getMessage().getInt(FRAME_3_IV_FRAGMENT);
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid()
|
public boolean isValid()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.voice;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.edac.BPTC_16_2;
|
||||||
|
import io.github.dsheirer.edac.Golay24;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.IServiceOptionsProvider;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NonStandardShortBurst;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.NullShortBurst;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurst;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.ShortBurstOpcode;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.TransmitInterrupt;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.UnknownShortBurst;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors audio call voice frame messaging to detect encrypted audio calls and extract the encryption parameters
|
||||||
|
* that are embedded into the six voice frames that comprise a voice super-frame.
|
||||||
|
* <p>
|
||||||
|
* See patent: <a href="https://patents.google.com/patent/EP2347540B1/en">...</a> - embedding encryption parameters in voice super frame
|
||||||
|
* See patent: <a href="https://patents.google.com/patent/US8271009B2">...</a> - TXI - interrupting voice transmissions
|
||||||
|
*/
|
||||||
|
public class VoiceSuperFrameProcessor
|
||||||
|
{
|
||||||
|
private static final int[] IV = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||||
|
33, 34, 35, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] CRC4 = new int[]{56, 57, 58, 59};
|
||||||
|
private boolean mEncrypted = false;
|
||||||
|
private byte[] mFragmentA;
|
||||||
|
private byte[] mFragmentB;
|
||||||
|
private byte[] mFragmentC;
|
||||||
|
private byte[] mFragmentD;
|
||||||
|
private byte[] mFragmentE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public VoiceSuperFrameProcessor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this collector is in collecting mode.
|
||||||
|
*
|
||||||
|
* @return true if collecting.
|
||||||
|
*/
|
||||||
|
public boolean isCollecting()
|
||||||
|
{
|
||||||
|
return mEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fully resets this collector
|
||||||
|
*/
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
mEncrypted = false;
|
||||||
|
softReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void softReset()
|
||||||
|
{
|
||||||
|
mFragmentA = null;
|
||||||
|
mFragmentB = null;
|
||||||
|
mFragmentC = null;
|
||||||
|
mFragmentD = null;
|
||||||
|
mFragmentE = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the IV fragments from voice frames A-E have been collected and this is an encrypted call.
|
||||||
|
*
|
||||||
|
* @return true if the fragments are non-null.
|
||||||
|
*/
|
||||||
|
private boolean isComplete()
|
||||||
|
{
|
||||||
|
return mEncrypted && mFragmentA != null && mFragmentB != null && mFragmentC != null && mFragmentD != null &&
|
||||||
|
mFragmentE != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process full link control message to determine if the current call sequence is encrypted.
|
||||||
|
* @param flc to inspect
|
||||||
|
*/
|
||||||
|
public void process(FullLCMessage flc)
|
||||||
|
{
|
||||||
|
if(flc instanceof IServiceOptionsProvider provider && provider.getServiceOptions().isEncrypted())
|
||||||
|
{
|
||||||
|
mEncrypted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the voice frame to extract the IV fragments and the encryption parameters.
|
||||||
|
*
|
||||||
|
* @param voiceMessage to process.
|
||||||
|
*/
|
||||||
|
public void process(VoiceMessage voiceMessage)
|
||||||
|
{
|
||||||
|
if(voiceMessage instanceof VoiceEMBMessage voiceEMB)
|
||||||
|
{
|
||||||
|
if(voiceEMB.getEMB().isValid() && voiceEMB.getEMB().isEncrypted())
|
||||||
|
{
|
||||||
|
mEncrypted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(voiceMessage.getSyncPattern())
|
||||||
|
{
|
||||||
|
case BASE_STATION_VOICE:
|
||||||
|
mFragmentA = voiceMessage.getIvFragments();
|
||||||
|
break;
|
||||||
|
case BS_VOICE_FRAME_B:
|
||||||
|
mFragmentB = voiceMessage.getIvFragments();
|
||||||
|
break;
|
||||||
|
case BS_VOICE_FRAME_C:
|
||||||
|
mFragmentC = voiceMessage.getIvFragments();
|
||||||
|
break;
|
||||||
|
case BS_VOICE_FRAME_D:
|
||||||
|
mFragmentD = voiceMessage.getIvFragments();
|
||||||
|
break;
|
||||||
|
case BS_VOICE_FRAME_E:
|
||||||
|
mFragmentE = voiceMessage.getIvFragments();
|
||||||
|
break;
|
||||||
|
case BS_VOICE_FRAME_F:
|
||||||
|
if(voiceMessage instanceof VoiceEMBMessage voiceFrame6)
|
||||||
|
{
|
||||||
|
BinaryMessage frameFFragment = voiceFrame6.getFLCFragment();
|
||||||
|
ShortBurst shortBurst = extractShortBurst(frameFFragment);
|
||||||
|
EmbeddedParameters embeddedParameters = new EmbeddedParameters(shortBurst);
|
||||||
|
|
||||||
|
if(isComplete())
|
||||||
|
{
|
||||||
|
String iv = extractIV(voiceMessage.getIvFragments());
|
||||||
|
embeddedParameters.setIv(iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
voiceFrame6.setEmbeddedParameters(embeddedParameters);
|
||||||
|
}
|
||||||
|
softReset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the short burst from the Voice Frame F FLC fragment payload and combines with the optionally available
|
||||||
|
* initialization vector to return a parameters object.
|
||||||
|
*
|
||||||
|
* @param frameFFragment containing the short-burst FLC fragment
|
||||||
|
* @return embedded parameters.
|
||||||
|
*/
|
||||||
|
private ShortBurst extractShortBurst(BinaryMessage frameFFragment)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage decoded = BPTC_16_2.decodeShortBurst(new CorrectedBinaryMessage(frameFFragment));
|
||||||
|
|
||||||
|
if(decoded == null)
|
||||||
|
{
|
||||||
|
return new NonStandardShortBurst(BPTC_16_2.deinterleave(new CorrectedBinaryMessage(frameFFragment)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortBurstOpcode opcode = ShortBurst.getOpcode(decoded);
|
||||||
|
return switch(opcode)
|
||||||
|
{
|
||||||
|
case NULL -> new NullShortBurst(decoded);
|
||||||
|
case ARC4_ENCRYPTION, AES128_ENCRYPTION, AES256_ENCRYPTION -> new EncryptionParameters(decoded);
|
||||||
|
case TXI_DELAY -> new TransmitInterrupt(decoded);
|
||||||
|
default -> new UnknownShortBurst(decoded);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the encryption initialization vector (IV) from the voice frame fragments, performs error detection and
|
||||||
|
* correction and if the error correction passes, returns the hex string version of the 32-bit IV.
|
||||||
|
*
|
||||||
|
* @param mFragmentF voice frame 6 IV fragments.
|
||||||
|
* @return 32-bit IV as hex string or null if the extraction process couldn't extract/correct the IV.
|
||||||
|
*/
|
||||||
|
private String extractIV(byte[] mFragmentF)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage reassembled = new CorrectedBinaryMessage(72);
|
||||||
|
reassembled.setByte(0, combine(mFragmentA[0], mFragmentB[0]));
|
||||||
|
reassembled.setByte(8, combine(mFragmentC[0], mFragmentD[0]));
|
||||||
|
reassembled.setByte(16, combine(mFragmentE[0], mFragmentF[0]));
|
||||||
|
reassembled.setByte(24, combine(mFragmentA[1], mFragmentB[1]));
|
||||||
|
reassembled.setByte(32, combine(mFragmentC[1], mFragmentD[1]));
|
||||||
|
reassembled.setByte(40, combine(mFragmentE[1], mFragmentF[1]));
|
||||||
|
reassembled.setByte(48, combine(mFragmentA[2], mFragmentB[2]));
|
||||||
|
reassembled.setByte(56, combine(mFragmentC[2], mFragmentD[2]));
|
||||||
|
reassembled.setByte(64, combine(mFragmentE[2], mFragmentF[2]));
|
||||||
|
|
||||||
|
int check1 = Golay24.checkAndCorrect(reassembled, 0);
|
||||||
|
int check2 = Golay24.checkAndCorrect(reassembled, 24);
|
||||||
|
int check3 = Golay24.checkAndCorrect(reassembled, 48);
|
||||||
|
|
||||||
|
if(check1 == 2 || check2 == 2 || check3 == 2)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int iv = reassembled.getInt(IV);
|
||||||
|
int checksum = reassembled.getInt(CRC4);
|
||||||
|
|
||||||
|
boolean passes = crc4(iv, checksum);
|
||||||
|
|
||||||
|
if(passes)
|
||||||
|
{
|
||||||
|
return String.format("%08X", iv);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return String.format("%08X", iv) + "(CRC-FAIL " + check1 + "/" + check2 + "/" + check3 + "/" +
|
||||||
|
reassembled.getCorrectedBitCount() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines the low nibble from the hi byte with the low nibble from the lo byte
|
||||||
|
*
|
||||||
|
* @param high byte containing a low order nibble
|
||||||
|
* @param low byte containing a low order nibble
|
||||||
|
* @return low order nibbles from hi and lo bytes combined.
|
||||||
|
*/
|
||||||
|
private byte combine(byte high, byte low)
|
||||||
|
{
|
||||||
|
return (byte)(((high & 0xF) << 4) | (low & 0xF));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a CRC value from the polynomial: x4 + x1 + 1 (0x13) using an initial fill of 0xF.
|
||||||
|
* @param value to calculate CRC from
|
||||||
|
* @param crc to compare
|
||||||
|
* @return true if the calculated CRC from the value matches the crc argument value.
|
||||||
|
*/
|
||||||
|
public static boolean crc4(int value, int crc)
|
||||||
|
{
|
||||||
|
long checksum = (value & 0x0FFFFFFFFL) << 4;
|
||||||
|
checksum ^= 0xF; //Initial fill
|
||||||
|
long polynomial = 0x013L << 31;
|
||||||
|
long checkBit = 0x1L << 35;
|
||||||
|
|
||||||
|
for(int x = 31; x >= 0; x--)
|
||||||
|
{
|
||||||
|
if((checksum & checkBit) == checkBit)
|
||||||
|
{
|
||||||
|
checksum ^= polynomial;
|
||||||
|
}
|
||||||
|
polynomial >>= 1;
|
||||||
|
checkBit >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)checksum == crc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.voice.embedded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters that are embedded in a DMR voice super frame.
|
||||||
|
* @param algorithm identifier
|
||||||
|
* @param key identifier
|
||||||
|
* @param iv initialization vector
|
||||||
|
*/
|
||||||
|
public class EmbeddedParameters
|
||||||
|
{
|
||||||
|
private ShortBurst mShortBurst;
|
||||||
|
private String mIv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param shortBurst payload
|
||||||
|
* @param iv initialization vector extracted from DMR voice super-frame
|
||||||
|
*/
|
||||||
|
public EmbeddedParameters(ShortBurst shortBurst)
|
||||||
|
{
|
||||||
|
mShortBurst = shortBurst;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(getShortBurst());
|
||||||
|
if(hasIv())
|
||||||
|
{
|
||||||
|
sb.append(" IV:").append(getIv());
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short Burst payload message
|
||||||
|
* @return short burst
|
||||||
|
*/
|
||||||
|
public ShortBurst getShortBurst()
|
||||||
|
{
|
||||||
|
return mShortBurst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional initialization vector (IV) decoded from the voice super-frame
|
||||||
|
* @return iv
|
||||||
|
*/
|
||||||
|
public String getIv()
|
||||||
|
{
|
||||||
|
return mIv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the optional IV value.
|
||||||
|
* @param iv to set
|
||||||
|
*/
|
||||||
|
public void setIv(String iv)
|
||||||
|
{
|
||||||
|
mIv = iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the optional IV is included.
|
||||||
|
* @return true if included
|
||||||
|
*/
|
||||||
|
public boolean hasIv()
|
||||||
|
{
|
||||||
|
return mIv != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.voice.embedded;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.EncryptionAlgorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption parameters short burst payload.
|
||||||
|
*/
|
||||||
|
public class EncryptionParameters extends ShortBurst
|
||||||
|
{
|
||||||
|
private static final int[] KEY = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] ALGORITHM = new int[]{8, 9, 10};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param message containing the de-interleaved and error-corrected short burst payload.
|
||||||
|
*/
|
||||||
|
public EncryptionParameters(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption key ID
|
||||||
|
* @return key ID
|
||||||
|
*/
|
||||||
|
public int getKey()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption algorithm value
|
||||||
|
* @return algorithm (0 - 7).
|
||||||
|
*/
|
||||||
|
public int getAlgorithmValue()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(ALGORITHM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encryption algorithm.
|
||||||
|
* @return algorithm
|
||||||
|
*/
|
||||||
|
public EncryptionAlgorithm getAlgorithm()
|
||||||
|
{
|
||||||
|
int algorithm = getAlgorithmValue();
|
||||||
|
|
||||||
|
return switch(algorithm)
|
||||||
|
{
|
||||||
|
case 1 -> EncryptionAlgorithm.DMRA_RC4;
|
||||||
|
case 4 -> EncryptionAlgorithm.DMRA_AES128;
|
||||||
|
case 5 -> EncryptionAlgorithm.DMRA_AES256;
|
||||||
|
default -> EncryptionAlgorithm.UNKNOWN;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append("ENCRYPTION ALGORITHM:");
|
||||||
|
if(getAlgorithm() == EncryptionAlgorithm.UNKNOWN)
|
||||||
|
{
|
||||||
|
sb.append(getAlgorithmValue());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(getAlgorithm());
|
||||||
|
}
|
||||||
|
sb.append(" KEY:").append(getKey());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.voice.embedded;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short burst payload that doesn't pass the BPTC 16 fec check.
|
||||||
|
*/
|
||||||
|
public class NonStandardShortBurst extends ShortBurst
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param message containing the delinterleaved and error-corrected short burst payload.
|
||||||
|
*/
|
||||||
|
public NonStandardShortBurst(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "NON-STANDARD SHORT BURST:" + getMessage().toHexString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.voice.embedded;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null (empty) short burst
|
||||||
|
*/
|
||||||
|
public class NullShortBurst extends ShortBurst
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param message containing the delinterleaved and error-corrected short burst payload.
|
||||||
|
*/
|
||||||
|
public NullShortBurst(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
setValid(passesCRC3());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "NULL SHORT BURST";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.voice.embedded;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for DMR Voice Frame F Short Burst from the EMB payload.
|
||||||
|
*/
|
||||||
|
public abstract class ShortBurst
|
||||||
|
{
|
||||||
|
private static final int[] CRC3 = new int[]{0, 1, 2};
|
||||||
|
private static final int[] OPCODE = new int[]{8, 9, 10};
|
||||||
|
private static final int[] FULL_MESSAGE = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||||
|
|
||||||
|
private CorrectedBinaryMessage mMessage;
|
||||||
|
private boolean mValid = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param message containing the delinterleaved and error-corrected short burst payload.
|
||||||
|
*/
|
||||||
|
public ShortBurst(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
mMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the underlying message.
|
||||||
|
*
|
||||||
|
* @return message
|
||||||
|
*/
|
||||||
|
public CorrectedBinaryMessage getMessage()
|
||||||
|
{
|
||||||
|
return mMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this message passes any CRC checks.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return mValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the valid CRC flag for this message
|
||||||
|
* @param valid true or false
|
||||||
|
*/
|
||||||
|
protected void setValid(boolean valid)
|
||||||
|
{
|
||||||
|
mValid = valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric opcode value.
|
||||||
|
*
|
||||||
|
* @return value.
|
||||||
|
*/
|
||||||
|
public int getOpcodeValue()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(OPCODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opcode for this message.
|
||||||
|
*
|
||||||
|
* @return opcode
|
||||||
|
*/
|
||||||
|
public ShortBurstOpcode getOpcode()
|
||||||
|
{
|
||||||
|
return getOpcode(getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static utility method to lookup the opcode from a short burst message.
|
||||||
|
*
|
||||||
|
* @param message containing a short burst
|
||||||
|
* @return opcode
|
||||||
|
*/
|
||||||
|
public static ShortBurstOpcode getOpcode(BinaryMessage message)
|
||||||
|
{
|
||||||
|
return ShortBurstOpcode.fromValue(message.getInt(OPCODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the message to determine if it passes for a CRC3. Not all Short Burst messages use the CRC3 check.
|
||||||
|
*
|
||||||
|
* @return true if it passes.
|
||||||
|
*/
|
||||||
|
public boolean passesCRC3()
|
||||||
|
{
|
||||||
|
int checksum = getMessage().getInt(FULL_MESSAGE);
|
||||||
|
int polynomial = 0xB << 7;
|
||||||
|
int checkBit = 0x1 << 10;
|
||||||
|
|
||||||
|
for(int x = 10; x >= 0; x--)
|
||||||
|
{
|
||||||
|
if(checksum == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((checksum & checkBit) == checkBit)
|
||||||
|
{
|
||||||
|
checksum ^= polynomial;
|
||||||
|
}
|
||||||
|
polynomial >>= 1;
|
||||||
|
checkBit >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.voice.embedded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short Burst opcode
|
||||||
|
* See: <a href="https://patents.google.com/patent/US8271009B2">...</a> Page 12
|
||||||
|
*/
|
||||||
|
public enum ShortBurstOpcode
|
||||||
|
{
|
||||||
|
NULL(0),
|
||||||
|
ARC4_ENCRYPTION(1),
|
||||||
|
TXI_DELAY(3),
|
||||||
|
AES128_ENCRYPTION(4),
|
||||||
|
AES256_ENCRYPTION(5),
|
||||||
|
UNKNOWN(-1);
|
||||||
|
|
||||||
|
private final int mValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param value of the opcode
|
||||||
|
*/
|
||||||
|
ShortBurstOpcode(int value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric value for the opcode
|
||||||
|
* @return value.
|
||||||
|
*/
|
||||||
|
private int getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup the enum entry from the specified value.
|
||||||
|
* @param value to lookup
|
||||||
|
* @return matching entry or UNKNOWN.
|
||||||
|
*/
|
||||||
|
public static ShortBurstOpcode fromValue(int value)
|
||||||
|
{
|
||||||
|
for(ShortBurstOpcode opcode: ShortBurstOpcode.values())
|
||||||
|
{
|
||||||
|
if(opcode.getValue() == value)
|
||||||
|
{
|
||||||
|
return opcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.voice.embedded;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmit Interrupt short burst
|
||||||
|
* <p>
|
||||||
|
* See: https://patents.google.com/patent/US8271009B2
|
||||||
|
*/
|
||||||
|
public class TransmitInterrupt extends ShortBurst
|
||||||
|
{
|
||||||
|
private static final int[] DELAY = new int[]{3, 4, 5, 6, 7};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param message containing the delinterleaved and error-corrected short burst payload.
|
||||||
|
*/
|
||||||
|
public TransmitInterrupt(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
setValid(passesCRC3());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "TRANSMIT INTERRUPT (TXI) AT " + getDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay to when another radio can interrupt the current call.
|
||||||
|
* @return delay string.
|
||||||
|
*/
|
||||||
|
public String getDelay()
|
||||||
|
{
|
||||||
|
int value = getMessage().getInt(DELAY);
|
||||||
|
|
||||||
|
switch(value)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return "ANY TIME";
|
||||||
|
case 2:
|
||||||
|
return "FRAME E";
|
||||||
|
case 4:
|
||||||
|
return "FRAME D";
|
||||||
|
case 6:
|
||||||
|
return "FRAME C";
|
||||||
|
case 8:
|
||||||
|
return "FRAME B";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN(" + value + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user