mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 17:17:29 -05:00
Compare commits
10 Commits
v0.5.0-alp
...
v0.5.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eaa3433034 | ||
|
|
838016c89f | ||
|
|
030a88f5a3 | ||
|
|
de3ba5529a | ||
|
|
7962758e05 | ||
|
|
0f9d1c0792 | ||
|
|
48be38a2d8 | ||
|
|
207893e5a8 | ||
|
|
e07d6fa854 | ||
|
|
e26698d0d8 |
@@ -33,7 +33,7 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.5.0-alpha2'
|
version = '0.5.0-alpha3'
|
||||||
sourceCompatibility = '13'
|
sourceCompatibility = '13'
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
private SourceManager mSourceManager;
|
private SourceManager mSourceManager;
|
||||||
private AliasModel mAliasModel;
|
private AliasModel mAliasModel;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
|
private List<Long> mUnTunableFrequencies = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the channel processing manager
|
* Constructs the channel processing manager
|
||||||
@@ -185,15 +186,25 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
if(channel.getSourceConfiguration() instanceof SourceConfigTuner)
|
if(channel.getSourceConfiguration() instanceof SourceConfigTuner)
|
||||||
{
|
{
|
||||||
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
|
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
|
||||||
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequency +
|
|
||||||
"] - " + ce.getMessage());
|
if(!mUnTunableFrequencies.contains(frequency))
|
||||||
|
{
|
||||||
|
mUnTunableFrequencies.add(frequency);
|
||||||
|
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequency +
|
||||||
|
"] - " + ce.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency)
|
else if(channel.getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency)
|
||||||
{
|
{
|
||||||
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel
|
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel
|
||||||
.getSourceConfiguration()).getFrequencies();
|
.getSourceConfiguration()).getFrequencies();
|
||||||
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequencies +
|
|
||||||
"] - " + ce.getMessage());
|
if(frequencies.size() > 0 && !mUnTunableFrequencies.contains(frequencies.get(0)))
|
||||||
|
{
|
||||||
|
mUnTunableFrequencies.add(frequencies.get(0));
|
||||||
|
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequencies +
|
||||||
|
"] - " + ce.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ public class AliasConfigurationEditor extends SplitPane
|
|||||||
mAliasTableView.setItems(getAliasSortedList());
|
mAliasTableView.setItems(getAliasSortedList());
|
||||||
mAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
mAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
mAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Alias>)c -> {
|
mAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Alias>)c -> {
|
||||||
setAliases(mAliasTableView.getSelectionModel().getSelectedItems());
|
Platform.runLater(() -> setAliases(mAliasTableView.getSelectionModel().getSelectedItems()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,19 @@ public class RadioReferenceDecoder
|
|||||||
*/
|
*/
|
||||||
public boolean isLSM(Site site)
|
public boolean isLSM(Site site)
|
||||||
{
|
{
|
||||||
return site != null && site.getModulation() != null && site.getModulation().contentEquals("LSM");
|
if(site != null)
|
||||||
|
{
|
||||||
|
if(site.getModulation() != null && site.getModulation().contentEquals("LSM"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(site.getDescription() != null && site.getDescription().contains("Simulcast"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -479,20 +479,19 @@ public class SiteEditor extends GridPane
|
|||||||
|
|
||||||
private void createControlChannel()
|
private void createControlChannel()
|
||||||
{
|
{
|
||||||
SiteFrequency control = null;
|
List<SiteFrequency> controls = new ArrayList<>();
|
||||||
|
|
||||||
for(SiteFrequency siteFrequency: getSiteFrequencyTableView().getItems())
|
for(SiteFrequency siteFrequency: getSiteFrequencyTableView().getItems())
|
||||||
{
|
{
|
||||||
if(isControl(siteFrequency) && siteFrequency.getFrequency() > 0.01)
|
if(isControl(siteFrequency) && siteFrequency.getFrequency() > 0.01)
|
||||||
{
|
{
|
||||||
control = siteFrequency;
|
controls.add(siteFrequency);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(control == null)
|
if(controls.isEmpty())
|
||||||
{
|
{
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Site Has No Control Channel", ButtonType.OK);
|
Alert alert = new Alert(Alert.AlertType.ERROR, "Site Has No Control Channel(s)", ButtonType.OK);
|
||||||
alert.setTitle("Create Channel Configuration");
|
alert.setTitle("Create Channel Configuration");
|
||||||
alert.setHeaderText("Can't Create Channel Configuration");
|
alert.setHeaderText("Can't Create Channel Configuration");
|
||||||
alert.initOwner((getCreateChannelConfigurationButton()).getScene().getWindow());
|
alert.initOwner((getCreateChannelConfigurationButton()).getScene().getWindow());
|
||||||
@@ -514,9 +513,24 @@ public class SiteEditor extends GridPane
|
|||||||
|
|
||||||
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(), mCurrentSystemInformation));
|
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(), mCurrentSystemInformation));
|
||||||
|
|
||||||
SourceConfigTuner sourceConfigTuner = new SourceConfigTuner();
|
if(controls.size() == 1)
|
||||||
sourceConfigTuner.setFrequency((long)(control.getFrequency() * 1E6));
|
{
|
||||||
channel.setSourceConfiguration(sourceConfigTuner);
|
SourceConfigTuner sourceConfigTuner = new SourceConfigTuner();
|
||||||
|
sourceConfigTuner.setFrequency((long)(controls.get(0).getFrequency() * 1E6));
|
||||||
|
channel.setSourceConfiguration(sourceConfigTuner);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SourceConfigTunerMultipleFrequency sourceConfig = new SourceConfigTunerMultipleFrequency();
|
||||||
|
|
||||||
|
for(SiteFrequency control: controls)
|
||||||
|
{
|
||||||
|
sourceConfig.addFrequency((long)(control.getFrequency() * 1E6));
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.setSourceConfiguration(sourceConfig);
|
||||||
|
}
|
||||||
|
|
||||||
mPlaylistManager.getChannelModel().addChannel(channel);
|
mPlaylistManager.getChannelModel().addChannel(channel);
|
||||||
|
|
||||||
if(getGoToChannelEditorCheckBox().isSelected())
|
if(getGoToChannelEditorCheckBox().isSelected())
|
||||||
@@ -548,7 +562,7 @@ public class SiteEditor extends GridPane
|
|||||||
|
|
||||||
if(siteFrequencies.isEmpty())
|
if(siteFrequencies.isEmpty())
|
||||||
{
|
{
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR, "Site Has No Control or Alternate Channels", ButtonType.OK);
|
Alert alert = new Alert(Alert.AlertType.ERROR, "Site Has No Control or Alternate Channel(s)", ButtonType.OK);
|
||||||
alert.setTitle("Create Channel Configuration");
|
alert.setTitle("Create Channel Configuration");
|
||||||
alert.setHeaderText("Can't Create Channel Configuration");
|
alert.setHeaderText("Can't Create Channel Configuration");
|
||||||
alert.initOwner((getCreateChannelConfigurationButton()).getScene().getWindow());
|
alert.initOwner((getCreateChannelConfigurationButton()).getScene().getWindow());
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ import org.apache.commons.io.FileUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logback and SLF4j logging implementation.
|
* Logback and SLF4j logging implementation.
|
||||||
@@ -126,6 +131,17 @@ public class ApplicationLog
|
|||||||
((ch.qos.logback.classic.Logger)logger).addAppender(mRollingFileAppender);
|
((ch.qos.logback.classic.Logger)logger).addAppender(mRollingFileAppender);
|
||||||
|
|
||||||
// StatusPrinter.print(loggerContext);
|
// StatusPrinter.print(loggerContext);
|
||||||
|
Attributes atts = findManifestAttributes();
|
||||||
|
if (atts != null) {
|
||||||
|
mLog.info("SDRTrunk Version : " + atts.getValue("Implementation-Version"));
|
||||||
|
mLog.info("Gradle Version : " + atts.getValue("Created-By"));
|
||||||
|
mLog.info("Build Timestamp : " + atts.getValue("Build-Timestamp"));
|
||||||
|
mLog.info("Build-JDK : " + atts.getValue("Build-JDK"));
|
||||||
|
mLog.info("Build OS : " + atts.getValue("Build-OS"));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
mLog.info("Failed to find build information.");
|
||||||
|
}
|
||||||
|
|
||||||
mLog.info("");
|
mLog.info("");
|
||||||
mLog.info("*******************************************************************");
|
mLog.info("*******************************************************************");
|
||||||
@@ -163,4 +179,25 @@ public class ApplicationLog
|
|||||||
mRollingFileAppender = null;
|
mRollingFileAppender = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Attributes findManifestAttributes() {
|
||||||
|
try {
|
||||||
|
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
|
||||||
|
while (resources.hasMoreElements()) {
|
||||||
|
try {
|
||||||
|
Manifest manifest = new Manifest(resources.nextElement().openStream());
|
||||||
|
Attributes atts = manifest.getMainAttributes();
|
||||||
|
Boolean hasTitle = atts.containsValue("sdrtrunk project");
|
||||||
|
if (hasTitle) {
|
||||||
|
return atts;
|
||||||
|
}
|
||||||
|
} catch (IOException E) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.module.decode.ip;
|
package io.github.dsheirer.module.decode.ip;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data packet interface
|
* Data packet interface
|
||||||
*/
|
*/
|
||||||
@@ -28,16 +31,21 @@ public interface IPacket
|
|||||||
/**
|
/**
|
||||||
* Header for the data packet.
|
* Header for the data packet.
|
||||||
*/
|
*/
|
||||||
public IHeader getHeader();
|
IHeader getHeader();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional payload for the packet
|
* Optional payload for the packet
|
||||||
* @return null if there is no payload
|
* @return null if there is no payload
|
||||||
*/
|
*/
|
||||||
public IPacket getPayload();
|
IPacket getPayload();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this packet contains an optional payload
|
* Indicates if this packet contains an optional payload
|
||||||
*/
|
*/
|
||||||
public boolean hasPayload();
|
boolean hasPayload();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of identifiers for this packet and any child payload packets.
|
||||||
|
*/
|
||||||
|
List<Identifier> getIdentifiers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ package io.github.dsheirer.module.decode.ip;
|
|||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
|
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.cellocator.MCGPHeader;
|
||||||
|
import io.github.dsheirer.module.decode.ip.cellocator.MCGPMessageFactory;
|
||||||
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;
|
||||||
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
||||||
@@ -82,15 +84,23 @@ public class PacketMessageFactory
|
|||||||
/**
|
/**
|
||||||
* Creates a UDP/IP packet payload parser
|
* Creates a UDP/IP packet payload parser
|
||||||
*
|
*
|
||||||
|
* @param sourcePort for the packet (to identify the protocol/format)
|
||||||
* @param destinationPort for the packet (to identify the protocol/format)
|
* @param destinationPort for the packet (to identify the protocol/format)
|
||||||
* @param binaryMessage containing the IP/UDP payload
|
* @param binaryMessage containing the IP/UDP payload
|
||||||
* @param offset in the message to the start of the payload
|
* @param offset in the message to the start of the payload
|
||||||
* @return constructed packet messge parser
|
* @return constructed packet messge parser
|
||||||
*/
|
*/
|
||||||
public static IPacket createUDPPayload(int destinationPort, BinaryMessage binaryMessage, int offset)
|
public static IPacket createUDPPayload(int sourcePort, int destinationPort, BinaryMessage binaryMessage, int offset)
|
||||||
{
|
{
|
||||||
switch(destinationPort)
|
switch(destinationPort)
|
||||||
{
|
{
|
||||||
|
case 231:
|
||||||
|
//Cellocator
|
||||||
|
if(MCGPHeader.isCellocatorMessage(binaryMessage, offset))
|
||||||
|
{
|
||||||
|
return MCGPMessageFactory.create(binaryMessage, offset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 4001: //Location Service
|
case 4001: //Location Service
|
||||||
break;
|
break;
|
||||||
case 4004: //XCMP Service
|
case 4004: //XCMP Service
|
||||||
@@ -103,6 +113,23 @@ public class PacketMessageFactory
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch(sourcePort)
|
||||||
|
{
|
||||||
|
case 231:
|
||||||
|
//Cellocator
|
||||||
|
if(MCGPHeader.isCellocatorMessage(binaryMessage, offset))
|
||||||
|
{
|
||||||
|
return MCGPMessageFactory.create(binaryMessage, offset);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//This is normally source or destination port 231, but can be on other ports as well.
|
||||||
|
if(MCGPHeader.isCellocatorMessage(binaryMessage, offset))
|
||||||
|
{
|
||||||
|
return MCGPMessageFactory.create(binaryMessage, offset);
|
||||||
|
}
|
||||||
|
|
||||||
return new UnknownPacket(binaryMessage, offset);
|
return new UnknownPacket(binaryMessage, offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,10 @@
|
|||||||
package io.github.dsheirer.module.decode.ip;
|
package io.github.dsheirer.module.decode.ip;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketHeader;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketHeader;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class UnknownCompressedHeaderPacket implements IPacket
|
public class UnknownCompressedHeaderPacket implements IPacket
|
||||||
{
|
{
|
||||||
@@ -87,4 +90,10 @@ public class UnknownCompressedHeaderPacket implements IPacket
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
package io.github.dsheirer.module.decode.ip;
|
package io.github.dsheirer.module.decode.ip;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class UnknownPacket implements IPacket
|
public class UnknownPacket implements IPacket
|
||||||
{
|
{
|
||||||
@@ -78,4 +81,10 @@ public class UnknownPacket implements IPacket
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,11 @@
|
|||||||
package io.github.dsheirer.module.decode.ip.ars;
|
package io.github.dsheirer.module.decode.ip.ars;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.ip.IPacket;
|
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.Packet;
|
import io.github.dsheirer.module.decode.ip.Packet;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ARSPacket extends Packet
|
public class ARSPacket extends Packet
|
||||||
{
|
{
|
||||||
@@ -64,4 +67,10 @@ public class ARSPacket extends Packet
|
|||||||
//There are no payloads for ARS packets ... the header parses all of the information.
|
//There are no payloads for ARS packets ... the header parses all of the information.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller (Inbound) Acknowledge Packet
|
||||||
|
*/
|
||||||
|
public class AcknowledgeMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] COMMAND_NUMERATOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
|
//This field is (possibly) byte reversed (ie big endian)
|
||||||
|
private static final int[] AUTHENTICATION_CODE = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 56, 57, 58, 59, 60, 61,
|
||||||
|
62, 63, 48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] ACTION_CODE = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] MAIN_ACKNOWLEDGE_NUMBER = new int[]{88, 89, 90, 91, 92, 93, 94, 95, 80, 81, 82, 83, 84,
|
||||||
|
85, 86, 87};
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] SECONDARY_ACKNOWLEDGE_NUMBER = new int[]{104, 105, 106, 107, 108, 109, 110, 111, 96, 97,
|
||||||
|
98, 99, 100, 101, 102, 103};
|
||||||
|
private static final int[] RESERVED = new int[]{112, 113, 114, 115, 116, 117, 118, 119};
|
||||||
|
private static final int[] YEAR = new int[]{121, 122, 123, 124, 125, 126, 127};
|
||||||
|
private static final int[] MONTH = new int[]{133, 134, 135, 120};
|
||||||
|
private static final int[] DAY = new int[]{128, 129, 130, 131, 132};
|
||||||
|
private static final int[] RESERVED_2 = new int[]{152, 153, 154, 155, 156, 157, 158}; //Always 0x40
|
||||||
|
private static final int[] HOUR = new int[]{139, 140, 141, 142, 143};
|
||||||
|
private static final int[] MINUTE = new int[]{149, 150, 151, 136, 137, 138};
|
||||||
|
private static final int[] SECOND = new int[]{159, 144, 145, 146, 147, 148};
|
||||||
|
|
||||||
|
private CellocatorRadioIdentifier mTargetRadioId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public AcknowledgeMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
if(mTargetRadioId == null)
|
||||||
|
{
|
||||||
|
mTargetRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(TARGET_UNIT_ID, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTargetRadioId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MCGPMessageType getAcknowledgedCommand()
|
||||||
|
{
|
||||||
|
int command = getMessage().getInt(COMMAND_NUMERATOR, getOffset());
|
||||||
|
return MCGPMessageType.getOutboundMessageType(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthenticationCode()
|
||||||
|
{
|
||||||
|
return Integer.toHexString(getMessage().getInt(AUTHENTICATION_CODE, getOffset())).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always set to zero for an acknowledge message
|
||||||
|
*/
|
||||||
|
public int getActionCode()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(ACTION_CODE, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMainAcknowledgeNumber()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(MAIN_ACKNOWLEDGE_NUMBER, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently not used and set to zero.
|
||||||
|
*/
|
||||||
|
public int getSecondaryAcknowledgeNumber()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(SECONDARY_ACKNOWLEDGE_NUMBER, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp()
|
||||||
|
{
|
||||||
|
Calendar calendar = new GregorianCalendar();
|
||||||
|
calendar.clear();
|
||||||
|
|
||||||
|
int year = 2000 + getMessage().getInt(YEAR, getOffset());
|
||||||
|
int month = getMessage().getInt(MONTH, getOffset()) - 1; //Calendar month is zero based
|
||||||
|
int day = getMessage().getInt(DAY, getOffset());
|
||||||
|
int hour = getMessage().getInt(HOUR, getOffset());
|
||||||
|
int minute = getMessage().getInt(MINUTE, getOffset());
|
||||||
|
int second = getMessage().getInt(SECOND, getOffset());
|
||||||
|
|
||||||
|
calendar.set(year, month, day, hour, minute, second);
|
||||||
|
|
||||||
|
return calendar.getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR RADIO:");
|
||||||
|
sb.append(getRadioId());
|
||||||
|
sb.append(" ACKNOWLEDGE:").append(getAcknowledgedCommand());
|
||||||
|
sb.append(" MESSAGE NUMBER:").append(getMainAcknowledgeNumber());
|
||||||
|
sb.append(" TIME:").append(new Date(getTimestamp()));
|
||||||
|
sb.append(" AUTH:").append(getAuthenticationCode());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cellocator Radio Identifier
|
||||||
|
*/
|
||||||
|
public class CellocatorRadioIdentifier extends RadioIdentifier
|
||||||
|
{
|
||||||
|
public CellocatorRadioIdentifier(Integer value, Role role)
|
||||||
|
{
|
||||||
|
super(value, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Protocol getProtocol()
|
||||||
|
{
|
||||||
|
return Protocol.CELLOCATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a TO radio identifier
|
||||||
|
*/
|
||||||
|
public static CellocatorRadioIdentifier createTo(int radioId)
|
||||||
|
{
|
||||||
|
return new CellocatorRadioIdentifier(radioId, Role.TO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a FROM radio identifier
|
||||||
|
*/
|
||||||
|
public static CellocatorRadioIdentifier createFrom(int radioId)
|
||||||
|
{
|
||||||
|
return new CellocatorRadioIdentifier(radioId, Role.FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ANY radio identifier
|
||||||
|
*/
|
||||||
|
public static CellocatorRadioIdentifier createAny(int radioId)
|
||||||
|
{
|
||||||
|
return new CellocatorRadioIdentifier(radioId, Role.ANY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cellocator - Commands Enumeration
|
||||||
|
*/
|
||||||
|
public enum Command
|
||||||
|
{
|
||||||
|
IMMEDIATE_STATUS_REQUEST(0x0, "IMMEDIATE STATUS AND LOCATION"),
|
||||||
|
UNIT_STATUS_CHANGE(0x2, "CHANGE UNIT STATUS"),
|
||||||
|
OUTPUT_STATE_CHANGE(0x3, "CHANGE OUTPUT STATE"),
|
||||||
|
DISABLE_ACTIVE_TRANSMISSIONS(0x4, "DISABLE ACTIVE TRANSMISSIONS"),
|
||||||
|
TRACKING_CONTROL(0x5, "TRACKING CONTROL"),
|
||||||
|
ALARM_CADENCE_CONTROL(0x6, "ALARM CADENCE CONTROL"),
|
||||||
|
COMMENCE_GRADUAL_ENGINE_STOP(0x7, "COMMENCE GRADUAL ENGINE STOP"),
|
||||||
|
INITIATE_CSD_SESSION(0xC, "INITIATE CSD SESSION"),
|
||||||
|
ERASE_TRACKING_LOG(0xD, "ERASE TRACKING LOG"),
|
||||||
|
RESET_GPS_RECEIVER(0xE, "RESET GPS RECEIVER"),
|
||||||
|
SEQUENCE_DETECTION_LEARN(0xF, "SEQUENCE DETECTION LEARN"),
|
||||||
|
FORCE_GPS_ENERGIZING(0x10, "FORCE GPS ACTIVATION"),
|
||||||
|
CONNECT_TO_SERVER(0x12, "CONNECT TO SERVER"),
|
||||||
|
MANUFACTURER_RESERVED(0x13, "MANUFACTURER RESERVED"),
|
||||||
|
CALIBRATE_FREQUENCY_COUNTERS(0x14, "CALIBRATE FREQUENCY COUNTERS"),
|
||||||
|
CONTROL_COM_PORT_TRANSPARENT_MODE(0x15, "CONTROL COM PORT TRANSPARENT MODE"),
|
||||||
|
QUERY_CONNECTED_TRAILER_ID(0x16, "QUERY CONNECTED TRAILER ID"),
|
||||||
|
CAMERA_SUPPORT(0x18, "CAMERA SUPPORT"),
|
||||||
|
NANO_WAKEUP_AND_STATUS_REQUEST(0x19, "NANO WAKEUP AND STATUS REQUEST"),
|
||||||
|
ENABLE_MODEM_FOTA_SESSION(0x1A, "ENABLE MODEM FOTA SESSION"),
|
||||||
|
PHSN_CONTROL(0x1B, "PHSN CONTROL"),
|
||||||
|
CALL_CONTROL(0x1C, "CALL SHARING 2 CONTROL"),
|
||||||
|
CELLO_TRACK_T(0x1D, "CELLO TRACK T"),
|
||||||
|
CALIBRATE_CAN_GPS_SPEED(0x1F, "CALIBRATE CAN GPS SPEED"),
|
||||||
|
|
||||||
|
UNKNOWN(-1, "UNKNOWN COMMAND");
|
||||||
|
|
||||||
|
private int mValue;
|
||||||
|
private String mLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value of the command
|
||||||
|
* @param label for pretty printing
|
||||||
|
*/
|
||||||
|
Command(int value, String label)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
mLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the command
|
||||||
|
*/
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<Integer,Command> LOOKUP_MAP = new TreeMap<>();
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
for(Command command: Command.values())
|
||||||
|
{
|
||||||
|
LOOKUP_MAP.put(command.getValue(), command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup a command from its value
|
||||||
|
* @param value of command to lookup
|
||||||
|
* @return command or UNKNOWN
|
||||||
|
*/
|
||||||
|
public static Command fromValue(int value)
|
||||||
|
{
|
||||||
|
if(LOOKUP_MAP.containsKey(value))
|
||||||
|
{
|
||||||
|
return LOOKUP_MAP.get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Communication Control Field parser
|
||||||
|
*/
|
||||||
|
public class CommunicationControl
|
||||||
|
{
|
||||||
|
public enum Initiative {ACTIVE, RESPONSE};
|
||||||
|
public enum SpeedType {MOMENTARY, MAX};
|
||||||
|
|
||||||
|
private static final int MASK_CAN_ORIGINATED_ODOMETER = 0x8000;
|
||||||
|
private static final int MASK_CAN_ORIGINATED_SPEED = 0x4000;
|
||||||
|
private static final int MASK_MULTI_PURPOSE_FIELD_CONTENT = 0x3000;
|
||||||
|
private static final int MASK_CR200_CR300_MESSAGE_SOURCE = 0x800;
|
||||||
|
private static final int MASK_GARMIN_CONNECTED = 0x400;
|
||||||
|
private static final int MASK_GARMIN_ENABLED = 0x200;
|
||||||
|
private static final int MASK_CR200_CR300_MESSAGE_INITIATIVE = 0x100;
|
||||||
|
private static final int MASK_CR200_CR300_GSM_HIBERNATION_INDICATOR = 0x80;
|
||||||
|
private static final int MASK_MOMENTARY_OR_MAX_SPEED_VALUE = 0x40;
|
||||||
|
private static final int MASK_BUSINESS_OR_PRIVATE_MODE = 0x20;
|
||||||
|
private static final int MASK_FIRMWARE_SUB_VERSION = 0x1F;
|
||||||
|
|
||||||
|
private int mValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param value of the 16-bit field
|
||||||
|
*/
|
||||||
|
public CommunicationControl(int value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the odometer value originated from the vehicle CAN bus
|
||||||
|
*/
|
||||||
|
public boolean isCANOriginatedOdometer()
|
||||||
|
{
|
||||||
|
return isSet(MASK_CAN_ORIGINATED_ODOMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the speed value originated from the vehicle CAN bus
|
||||||
|
*/
|
||||||
|
public boolean isCANOriginatedSpeed()
|
||||||
|
{
|
||||||
|
return isSet(MASK_CAN_ORIGINATED_SPEED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the Garmin GPS is connected to the unit
|
||||||
|
*/
|
||||||
|
public boolean isGarminConnected()
|
||||||
|
{
|
||||||
|
return isSet(MASK_GARMIN_CONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the Garmin GPS is enabled
|
||||||
|
*/
|
||||||
|
public boolean isGarminEnabled()
|
||||||
|
{
|
||||||
|
return isSet(MASK_GARMIN_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the message was initiated by the unit or if the message is a response to a request.
|
||||||
|
*/
|
||||||
|
public Initiative getMessageSource()
|
||||||
|
{
|
||||||
|
return isSet(MASK_CR200_CR300_MESSAGE_INITIATIVE) ? Initiative.RESPONSE : Initiative.ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the context of the speed value contained in the message, momentary or max recorded from last event.
|
||||||
|
*/
|
||||||
|
public SpeedType getSpeedType()
|
||||||
|
{
|
||||||
|
return isSet(MASK_MOMENTARY_OR_MAX_SPEED_VALUE) ? SpeedType.MAX : SpeedType.MOMENTARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the masked value is set for this field
|
||||||
|
* @param mask to check
|
||||||
|
* @return true if all set bits of the mask value are also set in the field bits
|
||||||
|
*/
|
||||||
|
private boolean isSet(int mask)
|
||||||
|
{
|
||||||
|
return (mValue & mask) == mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit (Outbound) data being forwarded over the wireless interface to a central application
|
||||||
|
*/
|
||||||
|
public class ForwardedDataMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] COMMAND_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] FORWARDED_MESSAGE_NUMBER = new int[]{88, 89, 90, 91, 92, 93, 94, 95};
|
||||||
|
private static final int[] FRAGMENT_CONTROL = new int[]{96, 97, 98, 99, 100, 101, 102, 103};
|
||||||
|
private static final int[] PAYLOAD_LENGTH = new int[]{112, 113, 114, 115, 116, 117, 118, 119, 104, 105, 106, 107,
|
||||||
|
108, 109, 110, 111};
|
||||||
|
private static final int PAYLOAD_START = 120;
|
||||||
|
|
||||||
|
private CellocatorRadioIdentifier mTargetRadioId;
|
||||||
|
private FragmentControl mFragmentControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public ForwardedDataMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Target radio ID to receive the forwarded data
|
||||||
|
*/
|
||||||
|
public CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
if(mTargetRadioId == null)
|
||||||
|
{
|
||||||
|
mTargetRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(TARGET_UNIT_ID, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTargetRadioId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message fragment control structure
|
||||||
|
*/
|
||||||
|
public FragmentControl getFragmentControl()
|
||||||
|
{
|
||||||
|
if(mFragmentControl == null)
|
||||||
|
{
|
||||||
|
mFragmentControl = new FragmentControl(getMessage().getInt(FRAGMENT_CONTROL, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFragmentControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This message number
|
||||||
|
*/
|
||||||
|
public int getMessageNumerator()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(COMMAND_NUMERATOR, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message number for the forwarded message
|
||||||
|
*/
|
||||||
|
public int getForwardedMessageNumerator()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(FORWARDED_MESSAGE_NUMBER, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the payload in bytes
|
||||||
|
*/
|
||||||
|
private int getPayloadLength()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(PAYLOAD_LENGTH, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hexadecimal representation of the payload data.
|
||||||
|
*/
|
||||||
|
public String getPayloadData()
|
||||||
|
{
|
||||||
|
int payloadEnd = PAYLOAD_START + (getPayloadLength() * 8);
|
||||||
|
return getMessage().get(PAYLOAD_START, payloadEnd).toHexString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR TO RADIO:");
|
||||||
|
sb.append(getRadioId());
|
||||||
|
sb.append(" MESSAGE NUMBER:").append(getMessageNumerator());
|
||||||
|
|
||||||
|
if(getHeader().getMessageType() == MCGPMessageType.OUTBOUND_FORWARDED_REALTIME_DATA)
|
||||||
|
{
|
||||||
|
sb.append(" FORWARDED SERIAL PORT DATA");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(" FORWARDED LOGGED DATA");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" MESSAGE NUMBER:").append(getForwardedMessageNumerator());
|
||||||
|
sb.append(" ").append(getFragmentControl());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides fragment information for messages that are too long and have to be fragmented.
|
||||||
|
*/
|
||||||
|
public class FragmentControl
|
||||||
|
{
|
||||||
|
public enum Origination{UNIT,APPLICATION};
|
||||||
|
private static final int BIT_MASK_ORIGINATION = 0x80;
|
||||||
|
private static final int BIT_MASK_LAST_FRAGMENT = 0x40;
|
||||||
|
private static final int BIT_MASK_FRAGMENT_INDEX = 0x3F;
|
||||||
|
private int mValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value of the fragment control field byte
|
||||||
|
*/
|
||||||
|
public FragmentControl(int value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(getOrigination().name());
|
||||||
|
sb.append(" ORIGINATED FRAGMENT #").append(getFragmentIndex());
|
||||||
|
|
||||||
|
if(isLastFragment())
|
||||||
|
{
|
||||||
|
sb.append(" FINAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the origination of the message fragment
|
||||||
|
* @return origination, unit or application
|
||||||
|
*/
|
||||||
|
public Origination getOrigination()
|
||||||
|
{
|
||||||
|
return isSet(BIT_MASK_ORIGINATION) ? Origination.UNIT : Origination.APPLICATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this is the last fragment
|
||||||
|
*/
|
||||||
|
public boolean isLastFragment()
|
||||||
|
{
|
||||||
|
return isSet(BIT_MASK_LAST_FRAGMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment index for this fragment, 0-31
|
||||||
|
*/
|
||||||
|
public int getFragmentIndex()
|
||||||
|
{
|
||||||
|
return (mValue & BIT_MASK_FRAGMENT_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the bits that are set in the bit mask are also set in the value field
|
||||||
|
*/
|
||||||
|
private boolean isSet(int bitMask)
|
||||||
|
{
|
||||||
|
return (mValue & bitMask) == bitMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller (Inbound) Generic Command
|
||||||
|
*/
|
||||||
|
public class GenericCommandMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] COMMAND_NUMERATOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
|
//This field is (possibly) byte reversed (ie big endian)
|
||||||
|
private static final int[] AUTHENTICATION_CODE = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 56, 57, 58, 59, 60, 61,
|
||||||
|
62, 63, 48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] COMMAND_CODE = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private CellocatorRadioIdentifier mTargetRadioId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public GenericCommandMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
if(mTargetRadioId == null)
|
||||||
|
{
|
||||||
|
mTargetRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(TARGET_UNIT_ID, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTargetRadioId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCommandNumerator()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(COMMAND_NUMERATOR, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthenticationCode()
|
||||||
|
{
|
||||||
|
return Integer.toHexString(getMessage().getInt(AUTHENTICATION_CODE, getOffset())).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCommandCode()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(COMMAND_CODE, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Command getCommand()
|
||||||
|
{
|
||||||
|
return Command.fromValue(getCommandCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR RADIO:");
|
||||||
|
sb.append(getRadioId());
|
||||||
|
sb.append(" REQUEST:").append(getCommand());
|
||||||
|
sb.append(" SEQUENCE:").append(getCommandNumerator());
|
||||||
|
sb.append(" AUTH:").append(getAuthenticationCode());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit (Outbound) Status and Location
|
||||||
|
*/
|
||||||
|
public class LocationStatusMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
private static final int[] SOURCE_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] COMMUNICATION_CONTROL = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
|
||||||
|
45, 46, 47};
|
||||||
|
private static final int[] MESSAGE_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] HARDWARE_VERSION = new int[]{56, 57, 58, 59, 60, 61, 62, 63};
|
||||||
|
private static final int[] SOFTWARE_VERSION = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
|
private static final int[] PROTOCOL_VERSION = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
private static final int[] UNIT_STATUS = new int[]{80, 81, 82, 83};
|
||||||
|
private static final int[] CURRENT_GSM_OPERATOR = new int[]{84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 152,
|
||||||
|
153, 154, 155, 156, 157, 158, 159};
|
||||||
|
private static final int[] TRANSMISSION_REASON_SPECIFIC_DATA = new int[]{96, 97, 98, 99, 100, 101, 102, 103};
|
||||||
|
private static final int[] TRANSMISSION_REASON = new int[]{104, 105, 106, 107, 108, 109, 110, 111};
|
||||||
|
private static final int[] UNIT_MODE_OF_OPERATION = new int[]{112, 113, 114, 115, 116, 117, 118, 119}; //Byte 20
|
||||||
|
private static final int[] UNIT_IO_STATUS = new int[]{120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
|
||||||
|
132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151};
|
||||||
|
private static final int[] ANALOG_INPUT_1 = new int[]{160, 161, 162, 163, 164, 165, 166, 167};
|
||||||
|
private static final int[] ANALOG_INPUT_2 = new int[]{168, 169, 170, 171, 172, 173, 174, 175};
|
||||||
|
private static final int[] ANALOG_INPUT_3 = new int[]{176, 177, 178, 179, 180, 181, 182, 183};
|
||||||
|
private static final int[] ANALOG_INPUT_4 = new int[]{184, 185, 186, 187, 188, 189, 190, 191};
|
||||||
|
private static final int[] MILEAGE_COUNTER = new int[]{192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203,
|
||||||
|
204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215};
|
||||||
|
private static final int[] MULTI_PURPOSE_FIELD = new int[]{216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226,
|
||||||
|
227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248,
|
||||||
|
249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263};
|
||||||
|
private static final int[] LAST_GPS_FIX = new int[]{264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276,
|
||||||
|
277, 278, 279};
|
||||||
|
private static final int[] LOCATION_STATUS = new int[]{280, 281, 282, 283, 284, 285, 286, 287};
|
||||||
|
private static final int[] GPS_MODE_1 = new int[]{288, 289, 290, 291, 292, 293, 294, 295};
|
||||||
|
private static final int[] GPS_MODE_2 = new int[]{296, 297, 298, 299, 300, 301, 302, 303};
|
||||||
|
private static final int[] GPS_SATELLITES_USED = new int[]{304, 305, 306, 307, 308, 309, 310, 311};
|
||||||
|
|
||||||
|
//Original signed 32-bit big endian field rearranged in little endian byte format
|
||||||
|
private static final int[] LONGITUDE = new int[]{336, 337, 338, 339, 340, 341, 342, 343, 328, 329, 330, 331, 332,
|
||||||
|
333, 334, 335, 320, 321, 322, 323, 324, 325, 326, 327, 312, 313, 314, 315, 316, 317, 318, 319};
|
||||||
|
|
||||||
|
//Original signed 32-bit big endian field rearranged in little endian byte format
|
||||||
|
private static final int[] LATITUDE = new int[]{368, 369, 370, 371, 372, 373, 374, 375, 360, 361, 362, 363, 364,
|
||||||
|
365, 366, 367, 352, 353, 354, 355, 356, 357, 358, 359, 344, 345, 346, 347, 348, 349, 350, 351};
|
||||||
|
|
||||||
|
//Original signed 32-bit big endian field rearranged in little endian byte format
|
||||||
|
private static final int[] ALTITUDE = new int[]{400, 401, 402, 403, 404, 405, 406, 407, 392, 393, 394, 395, 396,
|
||||||
|
397, 398, 399, 384, 385, 386, 387, 388, 389, 390, 391, 376, 377, 378, 379, 380, 381, 382, 383};
|
||||||
|
|
||||||
|
//Original signed 32-bit big endian field rearranged in little endian byte format
|
||||||
|
private static final int[] GROUND_SPEED = new int[]{432, 433, 434, 435, 436, 437, 438, 439, 424, 425, 426, 427, 428,
|
||||||
|
429, 430, 431, 416, 417, 418, 419, 420, 421, 422, 423, 408, 409, 410, 411, 412, 413, 414, 415};
|
||||||
|
|
||||||
|
//Original signed 32-bit big endian field rearranged in little endian byte format
|
||||||
|
private static final int[] HEADING_TRUE = new int[]{448, 449, 450, 451, 452, 453, 454, 455, 440, 441, 442, 443,
|
||||||
|
444, 445, 446, 447};
|
||||||
|
|
||||||
|
private static final int[] UTC_TIME_SECOND = new int[]{456, 457, 458, 459, 460, 461, 462, 463};
|
||||||
|
private static final int[] UTC_TIME_MINUTE = new int[]{464, 465, 466, 467, 468, 469, 470, 471};
|
||||||
|
private static final int[] UTC_TIME_HOUR = new int[]{472, 473, 474, 475, 476, 477, 478, 479};
|
||||||
|
private static final int[] UTC_TIME_DAY = new int[]{480, 481, 482, 483, 484, 485, 486, 487};
|
||||||
|
private static final int[] UTC_TIME_MONTH = new int[]{488, 489, 490, 491, 492, 493, 494, 495};
|
||||||
|
private static final int[] UTC_TIME_YEAR = new int[]{496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507,
|
||||||
|
508, 509, 510, 511};
|
||||||
|
|
||||||
|
private CellocatorRadioIdentifier mSourceRadioId;
|
||||||
|
private CommunicationControl mCommunicationControl;
|
||||||
|
private Long mUTCTimestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public LocationStatusMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio or unit ID
|
||||||
|
*/
|
||||||
|
public CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
if(mSourceRadioId == null)
|
||||||
|
{
|
||||||
|
mSourceRadioId = CellocatorRadioIdentifier.createFrom(getMessage().getInt(SOURCE_UNIT_ID, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadioId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Communication control bitmap field parser.
|
||||||
|
*/
|
||||||
|
public CommunicationControl getCommunicationControl()
|
||||||
|
{
|
||||||
|
if(mCommunicationControl == null)
|
||||||
|
{
|
||||||
|
mCommunicationControl = new CommunicationControl(getMessage().getInt(COMMUNICATION_CONTROL, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCommunicationControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message number. This is a one-up sequence maintained by the source unit that is reset to zero after
|
||||||
|
* each power cycle and used to track the message through receipt.
|
||||||
|
*/
|
||||||
|
public int getMessageNumerator()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(MESSAGE_NUMERATOR, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of satellites used by the GPS for calculating the position/fix.
|
||||||
|
*/
|
||||||
|
public int getSatelliteCount()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(GPS_SATELLITES_USED, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Latitude in degrees decimal format for WGS-84 datum
|
||||||
|
*/
|
||||||
|
public double getLatitude()
|
||||||
|
{
|
||||||
|
int value = getMessage().getInt(LATITUDE, getOffset());
|
||||||
|
double radians = (double)value / 1E8d;
|
||||||
|
return FastMath.toDegrees(radians);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Longitude in degrees decimal format for WGS-84 datum
|
||||||
|
*/
|
||||||
|
public double getLongitude()
|
||||||
|
{
|
||||||
|
int value = getMessage().getInt(LONGITUDE, getOffset());
|
||||||
|
double radians = (double)value / 1E8d;
|
||||||
|
return FastMath.toDegrees(radians);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Altitude in meters
|
||||||
|
*/
|
||||||
|
public double getAltitude()
|
||||||
|
{
|
||||||
|
int centimeters = getMessage().getInt(ALTITUDE, getOffset());
|
||||||
|
return (double)centimeters / 1E2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speed over ground in kph
|
||||||
|
*/
|
||||||
|
public double getSpeed()
|
||||||
|
{
|
||||||
|
int centimetersPerSecond = getMessage().getInt(GROUND_SPEED, getOffset());
|
||||||
|
double kilometersPerSecond = (double)centimetersPerSecond / 1E5d;
|
||||||
|
return kilometersPerSecond * 3600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heading relative to true North, 0-360 degrees.
|
||||||
|
*/
|
||||||
|
public double getHeading()
|
||||||
|
{
|
||||||
|
int headingThousandthsRadian = getMessage().getInt(HEADING_TRUE, getOffset());
|
||||||
|
double radians = (double)headingThousandthsRadian / 1E3d;
|
||||||
|
return FastMath.toDegrees(radians);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UTC timestamp for the location and status
|
||||||
|
* @return timestamp in milliseconds since epoch
|
||||||
|
*/
|
||||||
|
public long getUTCTimestamp()
|
||||||
|
{
|
||||||
|
if(mUTCTimestamp == null)
|
||||||
|
{
|
||||||
|
int year = getMessage().getInt(UTC_TIME_YEAR, getOffset());
|
||||||
|
int month = getMessage().getInt(UTC_TIME_MONTH, getOffset());
|
||||||
|
int day = getMessage().getInt(UTC_TIME_DAY, getOffset());
|
||||||
|
int hour = getMessage().getInt(UTC_TIME_HOUR, getOffset());
|
||||||
|
int minute = getMessage().getInt(UTC_TIME_MINUTE, getOffset());
|
||||||
|
int second = getMessage().getInt(UTC_TIME_SECOND, getOffset());
|
||||||
|
|
||||||
|
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
|
||||||
|
calendar.clear();
|
||||||
|
calendar.set(year, month, day, hour, minute, second);
|
||||||
|
mUTCTimestamp = calendar.getTimeInMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mUTCTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR RADIO:").append(getRadioId());
|
||||||
|
sb.append(" GPS LOCATION: ").append(getLatitude()).append(" ").append(getLongitude());
|
||||||
|
sb.append(" HEADING:").append(getHeading());
|
||||||
|
sb.append(" SPEED:").append(getSpeed()).append("kph");
|
||||||
|
sb.append(" AT:").append(new Date(getUTCTimestamp()));
|
||||||
|
sb.append(" MESSAGE #").append(getMessageNumerator());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* ******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* *****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.module.decode.ip.Header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cellocator MCGP Header
|
||||||
|
*/
|
||||||
|
public class MCGPHeader extends Header
|
||||||
|
{
|
||||||
|
private static final int[] M_CHARACTER = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] C_CHARACTER = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
|
private static final int[] G_CHARACTER = {16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] P_CHARACTER = {24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int M_VALUE = 0x4D;
|
||||||
|
private static final int C_VALUE = 0x43;
|
||||||
|
private static final int G_VALUE = 0x47;
|
||||||
|
private static final int P_VALUE = 0x50;
|
||||||
|
|
||||||
|
private static final int[] MESSAGE_TYPE = {32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
|
public static final int HEADER_LENGTH = 40;
|
||||||
|
|
||||||
|
private MCGPMessageType mMessageType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param message containing the header
|
||||||
|
* @param offset to the header within the message
|
||||||
|
*/
|
||||||
|
public MCGPHeader(BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs simple check of the first 32 bits of the message looking for the
|
||||||
|
* signature 'MCGP' header for cellocator messages.
|
||||||
|
* @param message to inspect
|
||||||
|
* @param offset to the cellocator payload
|
||||||
|
* @return true if the payload starts with MCGP
|
||||||
|
*/
|
||||||
|
public static boolean isCellocatorMessage(BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
return message.getInt(M_CHARACTER, offset) == M_VALUE &&
|
||||||
|
message.getInt(C_CHARACTER, offset) == C_VALUE &&
|
||||||
|
message.getInt(G_CHARACTER, offset) == G_VALUE &&
|
||||||
|
message.getInt(P_CHARACTER, offset) == P_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLength()
|
||||||
|
{
|
||||||
|
return HEADER_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR ").append(getMessageType());
|
||||||
|
|
||||||
|
if(getMessageType() == MCGPMessageType.UNKNOWN)
|
||||||
|
{
|
||||||
|
sb.append(" PACKET:").append(getMessage().toHexString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the type of message
|
||||||
|
*/
|
||||||
|
public MCGPMessageType getMessageType()
|
||||||
|
{
|
||||||
|
if(mMessageType == null)
|
||||||
|
{
|
||||||
|
mMessageType = getMessageType(getMessage(), getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMessageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets/Overrides the message type
|
||||||
|
*/
|
||||||
|
void setMessageType(MCGPMessageType type)
|
||||||
|
{
|
||||||
|
mMessageType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the type of MCGP message contained in the message starting at the offset index.
|
||||||
|
*/
|
||||||
|
public static MCGPMessageType getMessageType(BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
int payloadLengthBytes = (message.size() - offset) / 8;
|
||||||
|
return MCGPMessageType.fromValue(message.getInt(MESSAGE_TYPE, offset), payloadLengthBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric value for the message type for this header
|
||||||
|
*/
|
||||||
|
public int getMessageTypeValue()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(MESSAGE_TYPE, getOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* ******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* *****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an MCGP packet and header for parsing the contents of the packet.
|
||||||
|
*/
|
||||||
|
public class MCGPMessageFactory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Creates an MCGP packet message from the binary message at the specified binary message offset.
|
||||||
|
* @param message containing transmitted bits
|
||||||
|
* @param offset into the message for the start of the MCGP payload
|
||||||
|
* @return MCGP packet message parser
|
||||||
|
*/
|
||||||
|
public static MCGPPacket create(BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
MCGPHeader header = new MCGPHeader(message, offset);
|
||||||
|
int payloadOffset = offset + MCGPHeader.HEADER_LENGTH;
|
||||||
|
|
||||||
|
switch(header.getMessageType())
|
||||||
|
{
|
||||||
|
case INBOUND_GENERIC_COMMAND:
|
||||||
|
return new GenericCommandMessage(header, message, payloadOffset);
|
||||||
|
case INBOUND_GENERAL_ACKNOWLEDGE:
|
||||||
|
return new AcknowledgeMessage(header, message, payloadOffset);
|
||||||
|
case INBOUND_PROGRAMMING_COMMAND:
|
||||||
|
return new ProgrammingCommandMessage(header, message, payloadOffset);
|
||||||
|
case OUTBOUND_LOCATION_STATUS:
|
||||||
|
return new LocationStatusMessage(header, message, payloadOffset);
|
||||||
|
case OUTBOUND_PROGRAMMING_STATUS:
|
||||||
|
return new ProgrammingStatusMessage(header, message, payloadOffset);
|
||||||
|
case OUTBOUND_FORWARDED_REALTIME_DATA:
|
||||||
|
case OUTBOUND_FORWARDED_LOGGED_DATA_FRAGMENT:
|
||||||
|
return new ForwardedDataMessage(header, message, payloadOffset);
|
||||||
|
|
||||||
|
case INBOUND_MODULAR_REQUEST:
|
||||||
|
case OUTBOUND_MODULAR_RESPONSE:
|
||||||
|
//Both messages are variable length and use message type 9 so we have to inspect the total message
|
||||||
|
//length field for the response message which should be 0 for the request variant and non-zero for
|
||||||
|
//the response variant.
|
||||||
|
if(ModularResponseMessage.getTotalMessageLength(message, payloadOffset) > 0)
|
||||||
|
{
|
||||||
|
header.setMessageType(MCGPMessageType.OUTBOUND_MODULAR_RESPONSE);
|
||||||
|
return new ModularResponseMessage(header, message, payloadOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ModularRequestMessage(header, message, payloadOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
case UNKNOWN:
|
||||||
|
default:
|
||||||
|
return new UnknownMCGPMessage(header, message, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cellocator MCGP Message Types enumeration with indicated message lengths.
|
||||||
|
*
|
||||||
|
* Note: message length of -1 indicates a variable length message.
|
||||||
|
*/
|
||||||
|
public enum MCGPMessageType
|
||||||
|
{
|
||||||
|
INBOUND_GENERIC_COMMAND(0, 25 * 8, "COMMAND"),
|
||||||
|
INBOUND_PROGRAMMING_COMMAND(1, 34 * 8, "PROGRAMMING COMMAND"),
|
||||||
|
INBOUND_GENERAL_ACKNOWLEDGE(4, 28 * 8, "ACKNOWLEDGE"),
|
||||||
|
INBOUND_FORWARD_DATA_COMMAND(5, -1, "FORWARD DATA COMMAND"),
|
||||||
|
INBOUND_MODULAR_REQUEST(9, -1, "MODULAR MESSAGE REQUEST"),
|
||||||
|
|
||||||
|
OUTBOUND_LOCATION_STATUS(0, 70 * 8, "LOCATION AND STATUS"),
|
||||||
|
OUTBOUND_PROGRAMMING_STATUS(3, 31 * 8, "CONFIGURATION"),
|
||||||
|
OUTBOUND_FORWARDED_LOGGED_DATA_FRAGMENT(7, -1, "FORWARDED LOGGED DATA"),
|
||||||
|
OUTBOUND_FORWARDED_REALTIME_DATA(8, -1, "FORWARDED SERIAL_PORT DATA"),
|
||||||
|
OUTBOUND_MODULAR_RESPONSE(9, -1, "MODULAR MESSAGE RESPONSE"),
|
||||||
|
OUTBOUND_FIRMWARE_UPDATE(10,-1, "FIRMWARE UPDATE"),
|
||||||
|
|
||||||
|
UNKNOWN(-1, -1, "UNKNOWN");
|
||||||
|
|
||||||
|
private int mValue;
|
||||||
|
private int mLength;
|
||||||
|
private String mLabel;
|
||||||
|
|
||||||
|
MCGPMessageType(int value, int length, String label)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
mLength = length;
|
||||||
|
mLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnumSet<MCGPMessageType> INBOUND = EnumSet.range(INBOUND_GENERIC_COMMAND, INBOUND_MODULAR_REQUEST);
|
||||||
|
public static EnumSet<MCGPMessageType> OUTBOUND = EnumSet.range(OUTBOUND_LOCATION_STATUS, OUTBOUND_FIRMWARE_UPDATE);
|
||||||
|
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLength()
|
||||||
|
{
|
||||||
|
return mLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to lookup an entry from a value.
|
||||||
|
*/
|
||||||
|
public static MCGPMessageType fromValue(int value, int messageByteLength)
|
||||||
|
{
|
||||||
|
switch(value)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if(messageByteLength == 25)
|
||||||
|
{
|
||||||
|
return INBOUND_GENERIC_COMMAND;
|
||||||
|
}
|
||||||
|
else if(messageByteLength == 70)
|
||||||
|
{
|
||||||
|
return OUTBOUND_LOCATION_STATUS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
return INBOUND_PROGRAMMING_COMMAND;
|
||||||
|
case 3:
|
||||||
|
return OUTBOUND_PROGRAMMING_STATUS;
|
||||||
|
case 4:
|
||||||
|
return INBOUND_GENERAL_ACKNOWLEDGE;
|
||||||
|
case 5:
|
||||||
|
return INBOUND_FORWARD_DATA_COMMAND;
|
||||||
|
case 7:
|
||||||
|
return OUTBOUND_FORWARDED_LOGGED_DATA_FRAGMENT;
|
||||||
|
case 8:
|
||||||
|
return OUTBOUND_FORWARDED_REALTIME_DATA;
|
||||||
|
case 9:
|
||||||
|
//Defer to the message factory to detect inbound vs outbound message type
|
||||||
|
return INBOUND_MODULAR_REQUEST;
|
||||||
|
case 10:
|
||||||
|
return OUTBOUND_FIRMWARE_UPDATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MCGPMessageType getOutboundMessageType(int value)
|
||||||
|
{
|
||||||
|
for(MCGPMessageType type: OUTBOUND)
|
||||||
|
{
|
||||||
|
if(type.getValue() == value)
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* ******************************************************************************
|
||||||
|
* sdrtrunk
|
||||||
|
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* *****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.Packet;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cellocator MCGP Packet
|
||||||
|
*/
|
||||||
|
public abstract class MCGPPacket extends Packet
|
||||||
|
{
|
||||||
|
private MCGPHeader mHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public MCGPPacket(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(message, offset);
|
||||||
|
mHeader = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MCGPHeader getHeader()
|
||||||
|
{
|
||||||
|
return mHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit ID (to/from) for this packet
|
||||||
|
*/
|
||||||
|
protected abstract CellocatorRadioIdentifier getRadioId();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPacket getPayload()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
CellocatorRadioIdentifier radioIdentifier = getRadioId();
|
||||||
|
|
||||||
|
if(radioIdentifier != null)
|
||||||
|
{
|
||||||
|
return Collections.singletonList(radioIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modular message data types
|
||||||
|
*/
|
||||||
|
public enum ModularDataType
|
||||||
|
{
|
||||||
|
FIRMWARE_PLATFORM_MANIFEST(1),
|
||||||
|
CAN_DATA(2),
|
||||||
|
CAN_TRIGGER_DATA(3),
|
||||||
|
TIME_AND_LOCATION_DATA(4),
|
||||||
|
ACCELEROMETER_DATA(5),
|
||||||
|
PSP_ALARM_SYSTEM_DATA(6),
|
||||||
|
USAGE_COUNTER_DATA(7),
|
||||||
|
COMMAND_AUTHENTICATION_TABLE_DATA(8),
|
||||||
|
GSM_NEIGHBOR_LIST(9),
|
||||||
|
MAINTENANCE_SERVER_PLATFORM_MANIFEST(10),
|
||||||
|
UNKNOWN(0);
|
||||||
|
|
||||||
|
private int mValue;
|
||||||
|
|
||||||
|
ModularDataType(int value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModularDataType fromValue(int value)
|
||||||
|
{
|
||||||
|
for(ModularDataType type: ModularDataType.values())
|
||||||
|
{
|
||||||
|
if(type.getValue() == value)
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inbound (from Controller) Modular Request Message
|
||||||
|
*/
|
||||||
|
public class ModularRequestMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] COMMAND_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] PACKET_CONTROL = new int[]{88, 89, 90, 91, 92, 93, 94, 95};
|
||||||
|
private static final int[] TOTAL_MESSAGE_LENGTH = new int[]{96, 97, 98, 99, 100, 101, 102, 103};
|
||||||
|
private static final int[] SUB_DATA_1_MESSAGE_TYPE = new int[]{104, 105, 106, 107, 108, 109, 110, 111};
|
||||||
|
|
||||||
|
private CellocatorRadioIdentifier mTargetRadioId;
|
||||||
|
private PacketControl mPacketControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for the message.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public ModularRequestMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static access to message total length (bytes) field.
|
||||||
|
* @param message containing modular request message
|
||||||
|
* @param offset to start of modular request message
|
||||||
|
* @return value from the total message length field
|
||||||
|
*/
|
||||||
|
public static int getTotalMessageLength(BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
return message.getInt(TOTAL_MESSAGE_LENGTH, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radio/Unit identifier, either source or target as specified in the PacketControl field.
|
||||||
|
*/
|
||||||
|
public CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
if(mTargetRadioId == null)
|
||||||
|
{
|
||||||
|
int radioId = getMessage().getInt(TARGET_UNIT_ID, getOffset());
|
||||||
|
mTargetRadioId = CellocatorRadioIdentifier.createTo(radioId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTargetRadioId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketControl getPacketControl()
|
||||||
|
{
|
||||||
|
if(mPacketControl == null)
|
||||||
|
{
|
||||||
|
mPacketControl = new PacketControl(getMessage().getInt(PACKET_CONTROL, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPacketControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCommandNumerator()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(COMMAND_NUMERATOR, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modular data type for the first sub-chunk
|
||||||
|
*/
|
||||||
|
public ModularDataType getModularDataType()
|
||||||
|
{
|
||||||
|
return ModularDataType.fromValue(getMessage().getInt(SUB_DATA_1_MESSAGE_TYPE, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR RADIO:");
|
||||||
|
sb.append(getRadioId());
|
||||||
|
sb.append(" MODULAR REQUEST TYPE:").append(getModularDataType());
|
||||||
|
sb.append(" MESSAGE NUMBER:").append(getCommandNumerator());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modular Message
|
||||||
|
*/
|
||||||
|
public class ModularResponseMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] COMMUNICATION_CONTROL = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
|
||||||
|
46, 47};
|
||||||
|
private static final int[] MESSAGE_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] PACKET_CONTROL = new int[]{56, 57, 58, 59, 60, 61, 62, 63};
|
||||||
|
private static final int[] TOTAL_MESSAGE_LENGTH = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
|
private static final int[] SUB_DATA_1_MESSAGE_TYPE = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private CellocatorRadioIdentifier mRadioId;
|
||||||
|
private CommunicationControl mCommunicationControl;
|
||||||
|
private PacketControl mPacketControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for the message.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public ModularResponseMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static access to message total length (bytes) field.
|
||||||
|
* @param message containing modular response message
|
||||||
|
* @param offset to start of modular response message
|
||||||
|
* @return value from the total message length field
|
||||||
|
*/
|
||||||
|
public static int getTotalMessageLength(BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
return message.getInt(TOTAL_MESSAGE_LENGTH, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Radio/Unit identifier, either source or target as specified in the PacketControl field.
|
||||||
|
*/
|
||||||
|
public CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
if(mRadioId == null)
|
||||||
|
{
|
||||||
|
int radioId = getMessage().getInt(UNIT_ID, getOffset());
|
||||||
|
|
||||||
|
if(getPacketControl().getDirection() == PacketControl.Direction.FROM_UNIT)
|
||||||
|
{
|
||||||
|
mRadioId = CellocatorRadioIdentifier.createFrom(radioId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mRadioId = CellocatorRadioIdentifier.createTo(radioId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mRadioId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketControl getPacketControl()
|
||||||
|
{
|
||||||
|
if(mPacketControl == null)
|
||||||
|
{
|
||||||
|
mPacketControl = new PacketControl(getMessage().getInt(PACKET_CONTROL, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPacketControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommunicationControl getCommunicationControl()
|
||||||
|
{
|
||||||
|
if(mCommunicationControl == null)
|
||||||
|
{
|
||||||
|
mCommunicationControl = new CommunicationControl(getMessage().getInt(COMMUNICATION_CONTROL, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCommunicationControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageNumerator()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(MESSAGE_NUMERATOR, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modular data type for the first sub-chunk
|
||||||
|
*/
|
||||||
|
public ModularDataType getModularDataType()
|
||||||
|
{
|
||||||
|
return ModularDataType.fromValue(getMessage().getInt(SUB_DATA_1_MESSAGE_TYPE, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR RADIO:");
|
||||||
|
if(getPacketControl().getDirection() == PacketControl.Direction.FROM_UNIT)
|
||||||
|
{
|
||||||
|
sb.append(" FROM:");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(" TO:");
|
||||||
|
}
|
||||||
|
sb.append(getRadioId());
|
||||||
|
sb.append(" MODULAR TYPE:").append(getModularDataType());
|
||||||
|
sb.append(" MESSAGE NUMBER:").append(getMessageNumerator());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides fragment information for messages that are too long and have to be fragmented.
|
||||||
|
*/
|
||||||
|
public class PacketControl
|
||||||
|
{
|
||||||
|
public enum Direction {TO_UNIT,FROM_UNIT};
|
||||||
|
private static final int BIT_MASK_DIRECTION = 0x80;
|
||||||
|
private static final int BIT_MASK_OUT_OF_SPACE = 0x40;
|
||||||
|
private int mValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value of the fragment control field byte
|
||||||
|
*/
|
||||||
|
public PacketControl(int value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(" MODULAR PACKET ").append(getDirection());
|
||||||
|
|
||||||
|
if(isOutOfSpace())
|
||||||
|
{
|
||||||
|
sb.append(" (MESSAGE EXCEEDED SPACE)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the origination of the message fragment
|
||||||
|
* @return origination, unit or application
|
||||||
|
*/
|
||||||
|
public Direction getDirection()
|
||||||
|
{
|
||||||
|
return isSet(BIT_MASK_DIRECTION) ? Direction.TO_UNIT : Direction.FROM_UNIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the message contents exceeded the available space in this message
|
||||||
|
*/
|
||||||
|
public boolean isOutOfSpace()
|
||||||
|
{
|
||||||
|
return isSet(BIT_MASK_OUT_OF_SPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the bits that are set in the bit mask are also set in the value field
|
||||||
|
*/
|
||||||
|
private boolean isSet(int bitMask)
|
||||||
|
{
|
||||||
|
return (mValue & bitMask) == bitMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller (Inbound) Programming Command
|
||||||
|
*/
|
||||||
|
public class ProgrammingCommandMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] TARGET_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] COMMAND_NUMERATOR = new int[]{32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
|
//This field is (possibly) byte reversed (ie big endian)
|
||||||
|
private static final int[] AUTHENTICATION_CODE = new int[]{64, 65, 66, 67, 68, 69, 70, 71, 56, 57, 58, 59, 60, 61,
|
||||||
|
62, 63, 48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] BLOCK_CODE = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
private static final int[] BITMASK = new int[]{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95};
|
||||||
|
private static final int DATA_BLOCK_START = 96;
|
||||||
|
private static final int DATA_BLOCK_END = 224;
|
||||||
|
|
||||||
|
private CellocatorRadioIdentifier mTargetRadioId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public ProgrammingCommandMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
if(mTargetRadioId == null)
|
||||||
|
{
|
||||||
|
mTargetRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(TARGET_UNIT_ID, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTargetRadioId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCommandNumerator()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(COMMAND_NUMERATOR, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthenticationCode()
|
||||||
|
{
|
||||||
|
return Integer.toHexString(getMessage().getInt(AUTHENTICATION_CODE, getOffset())).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockCode()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(BLOCK_CODE, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBitMask()
|
||||||
|
{
|
||||||
|
return StringUtils.leftPad(Integer.toHexString(getMessage().getInt(BITMASK, getOffset())).toUpperCase(), 4, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDataBlock()
|
||||||
|
{
|
||||||
|
return getMessage().getSubMessage(DATA_BLOCK_START + getOffset(), DATA_BLOCK_END + getOffset()).toHexString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR RADIO:");
|
||||||
|
sb.append(getRadioId());
|
||||||
|
sb.append(" PROGRAM SEQUENCE NUMBER:").append(getCommandNumerator());
|
||||||
|
sb.append(" AUTH:").append(getAuthenticationCode());
|
||||||
|
sb.append(" BLOCK:").append(getBlockCode());
|
||||||
|
sb.append(" BITMASK:").append(getBitMask());
|
||||||
|
sb.append(" DATA:").append(getDataBlock());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit (Outbound) Programming Status/Response
|
||||||
|
*/
|
||||||
|
public class ProgrammingStatusMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
//This field is byte reversed (ie big endian)
|
||||||
|
private static final int[] SOURCE_UNIT_ID = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22,
|
||||||
|
23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] COMMUNICATION_CONTROL = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
|
||||||
|
46, 47};
|
||||||
|
private static final int[] MESSAGE_NUMERATOR = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] SPARE = new int[]{56, 57, 58, 59, 60, 61, 62, 63};
|
||||||
|
private static final int[] BLOCK_CODE = new int[]{64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
|
private static final int DATA_BLOCK_START = 72;
|
||||||
|
private static final int DATA_BLOCK_END = DATA_BLOCK_START + 128;
|
||||||
|
|
||||||
|
private CellocatorRadioIdentifier mSourceRadioId;
|
||||||
|
private CommunicationControl mCommunicationControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public ProgrammingStatusMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
if(mSourceRadioId == null)
|
||||||
|
{
|
||||||
|
mSourceRadioId = CellocatorRadioIdentifier.createTo(getMessage().getInt(SOURCE_UNIT_ID, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadioId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommunicationControl getCommunicationControl()
|
||||||
|
{
|
||||||
|
if(mCommunicationControl == null)
|
||||||
|
{
|
||||||
|
mCommunicationControl = new CommunicationControl(getMessage().getInt(COMMUNICATION_CONTROL, getOffset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCommunicationControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageNumerator()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(MESSAGE_NUMERATOR, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockCode()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(BLOCK_CODE, getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDataBlock()
|
||||||
|
{
|
||||||
|
return getMessage().getSubMessage(DATA_BLOCK_START + getOffset(), DATA_BLOCK_END + getOffset()).toHexString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR RADIO:");
|
||||||
|
sb.append(getRadioId());
|
||||||
|
sb.append(" PROGRAM MESSAGE NUMBER:").append(getMessageNumerator());
|
||||||
|
sb.append(" BLOCK:").append(getBlockCode());
|
||||||
|
sb.append(" DATA:").append(getDataBlock());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.ip.cellocator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MCGP Packet - Unknown Message Type
|
||||||
|
*/
|
||||||
|
public class UnknownMCGPMessage extends MCGPPacket
|
||||||
|
{
|
||||||
|
private IPacket mPayload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a parser for a header contained within a binary message starting at the offset.
|
||||||
|
*
|
||||||
|
* @param header for this message
|
||||||
|
* @param message containing the packet
|
||||||
|
* @param offset to the packet within the message
|
||||||
|
*/
|
||||||
|
public UnknownMCGPMessage(MCGPHeader header, BinaryMessage message, int offset)
|
||||||
|
{
|
||||||
|
super(header, message, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("CELLOCATOR UNKNOWN MESSAGE TYPE [");
|
||||||
|
sb.append(getHeader().getMessageTypeValue()).append("] ").append(getPayload());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPacket getPayload()
|
||||||
|
{
|
||||||
|
if(mPayload == null)
|
||||||
|
{
|
||||||
|
mPayload = new UnknownPacket(getMessage(), getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CellocatorRadioIdentifier getRadioId()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,10 +21,13 @@
|
|||||||
package io.github.dsheirer.module.decode.ip.ipv4;
|
package io.github.dsheirer.module.decode.ip.ipv4;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.ip.IPacket;
|
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.Packet;
|
import io.github.dsheirer.module.decode.ip.Packet;
|
||||||
import io.github.dsheirer.module.decode.ip.PacketMessageFactory;
|
import io.github.dsheirer.module.decode.ip.PacketMessageFactory;
|
||||||
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IPV4 packet
|
* IPV4 packet
|
||||||
@@ -33,6 +36,7 @@ public class IPV4Packet extends Packet
|
|||||||
{
|
{
|
||||||
private IPV4Header mHeader;
|
private IPV4Header mHeader;
|
||||||
private IPacket mPayload;
|
private IPacket mPayload;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an IPV4 packet parser
|
* Constructs an IPV4 packet parser
|
||||||
@@ -109,4 +113,23 @@ public class IPV4Packet extends Packet
|
|||||||
|
|
||||||
return mPayload;
|
return mPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
|
||||||
|
if(hasPayload())
|
||||||
|
{
|
||||||
|
mIdentifiers.addAll(getPayload().getIdentifiers());
|
||||||
|
}
|
||||||
|
|
||||||
|
mIdentifiers.add(getHeader().getToAddress());
|
||||||
|
mIdentifiers.add(getHeader().getFromAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,15 @@
|
|||||||
package io.github.dsheirer.module.decode.ip.udp;
|
package io.github.dsheirer.module.decode.ip.udp;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.module.decode.ip.IPacket;
|
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.Packet;
|
import io.github.dsheirer.module.decode.ip.Packet;
|
||||||
import io.github.dsheirer.module.decode.ip.PacketMessageFactory;
|
import io.github.dsheirer.module.decode.ip.PacketMessageFactory;
|
||||||
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
import io.github.dsheirer.module.decode.ip.UnknownPacket;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class UDPPacket extends Packet
|
public class UDPPacket extends Packet
|
||||||
{
|
{
|
||||||
private UDPHeader mHeader;
|
private UDPHeader mHeader;
|
||||||
@@ -73,16 +77,31 @@ public class UDPPacket extends Packet
|
|||||||
@Override
|
@Override
|
||||||
public IPacket getPayload()
|
public IPacket getPayload()
|
||||||
{
|
{
|
||||||
if(mPayload == null && getHeader().isValid())
|
if(mPayload == null)
|
||||||
{
|
{
|
||||||
mPayload = PacketMessageFactory.createUDPPayload(getHeader().getDestinationPort(), getMessage(), getOffset() + getHeader().getLength());
|
if(getHeader().isValid())
|
||||||
}
|
{
|
||||||
else
|
mPayload = PacketMessageFactory.createUDPPayload(getHeader().getSourcePort(),
|
||||||
{
|
getHeader().getDestinationPort(), getMessage(), getOffset() + getHeader().getLength());
|
||||||
//Set the offset to the message offset plus 64 bits for UDP header
|
}
|
||||||
mPayload = new UnknownPacket(getMessage(), getOffset() + 64);
|
else
|
||||||
|
{
|
||||||
|
//Set the offset to the message offset plus 64 bits for UDP header
|
||||||
|
mPayload = new UnknownPacket(getMessage(), getOffset() + 64);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mPayload;
|
return mPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(hasPayload())
|
||||||
|
{
|
||||||
|
return getPayload().getIdentifiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ 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.ip.IPacket;
|
import io.github.dsheirer.module.decode.ip.IPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
|
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.cellocator.MCGPPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
import io.github.dsheirer.module.decode.ip.ipv4.IPV4Packet;
|
||||||
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
|
||||||
import io.github.dsheirer.module.decode.p25.P25DecodeEvent;
|
import io.github.dsheirer.module.decode.p25.P25DecodeEvent;
|
||||||
@@ -935,6 +936,10 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
ARSPacket arsPacket = (ARSPacket)udpPayload;
|
ARSPacket arsPacket = (ARSPacket)udpPayload;
|
||||||
|
|
||||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
for(Identifier identifier: packet.getIdentifiers())
|
||||||
|
{
|
||||||
|
ic.update(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
@@ -945,9 +950,32 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
|||||||
|
|
||||||
broadcast(packetEvent);
|
broadcast(packetEvent);
|
||||||
}
|
}
|
||||||
|
else if(udpPayload instanceof MCGPPacket)
|
||||||
|
{
|
||||||
|
MCGPPacket mcgpPacket = (MCGPPacket)udpPayload;
|
||||||
|
|
||||||
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
for(Identifier identifier: packet.getIdentifiers())
|
||||||
|
{
|
||||||
|
ic.update(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodeEvent cellocatorEvent = P25DecodeEvent.builder(message.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
|
.eventDescription("Cellocator")
|
||||||
|
.identifiers(ic)
|
||||||
|
.details(mcgpPacket.toString() + " " + ipv4.toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
broadcast(cellocatorEvent);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||||
|
for(Identifier identifier: packet.getIdentifiers())
|
||||||
|
{
|
||||||
|
ic.update(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
|
||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.alias.Alias;
|
|||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.alias.id.record.Record;
|
import io.github.dsheirer.alias.id.record.Record;
|
||||||
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.bits.BitSetFullException;
|
import io.github.dsheirer.bits.BitSetFullException;
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
@@ -34,7 +33,6 @@ import io.github.dsheirer.dsp.psk.pll.IPhaseLockedLoop;
|
|||||||
import io.github.dsheirer.dsp.symbol.Dibit;
|
import io.github.dsheirer.dsp.symbol.Dibit;
|
||||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.message.IMessageProvider;
|
|
||||||
import io.github.dsheirer.message.Message;
|
import io.github.dsheirer.message.Message;
|
||||||
import io.github.dsheirer.message.MessageProviderModule;
|
import io.github.dsheirer.message.MessageProviderModule;
|
||||||
import io.github.dsheirer.message.StuffBitsMessage;
|
import io.github.dsheirer.message.StuffBitsMessage;
|
||||||
@@ -42,23 +40,17 @@ import io.github.dsheirer.message.SyncLossMessage;
|
|||||||
import io.github.dsheirer.module.ProcessingChain;
|
import io.github.dsheirer.module.ProcessingChain;
|
||||||
import io.github.dsheirer.module.decode.DecoderType;
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
||||||
import io.github.dsheirer.module.decode.p25.audio.P25P2AudioModule;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25MessageFactory;
|
import io.github.dsheirer.module.decode.p25.phase1.message.P25MessageFactory;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessageFactory;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUMessageFactory;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence;
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.PDUSequence;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
|
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessage;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessageFactory;
|
import io.github.dsheirer.module.decode.p25.phase1.message.tsbk.TSBKMessageFactory;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.P25P2MessageFramer;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.P25P2MessageProcessor;
|
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
|
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.record.AudioRecordingManager;
|
import io.github.dsheirer.record.AudioRecordingManager;
|
||||||
import io.github.dsheirer.record.binary.BinaryReader;
|
import io.github.dsheirer.record.binary.BinaryReader;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -201,6 +193,17 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
|
|||||||
{
|
{
|
||||||
mBinaryMessage.add(dibit.getBit1());
|
mBinaryMessage.add(dibit.getBit1());
|
||||||
mBinaryMessage.add(dibit.getBit2());
|
mBinaryMessage.add(dibit.getBit2());
|
||||||
|
|
||||||
|
if(mBinaryMessage.isFull())
|
||||||
|
{
|
||||||
|
//TDU's have a trailing status symbol that has to be removed -- set flag to true to suppress it.
|
||||||
|
if(mDataUnitID.hasTrailingStatusDibit())
|
||||||
|
{
|
||||||
|
mTrailingDibitsToSuppress = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(BitSetFullException bsfe)
|
catch(BitSetFullException bsfe)
|
||||||
{
|
{
|
||||||
@@ -209,17 +212,6 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
|
|||||||
//Reset so that we can start over again
|
//Reset so that we can start over again
|
||||||
reset(0);
|
reset(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mBinaryMessage.isFull())
|
|
||||||
{
|
|
||||||
//TDU's have a trailing status symbol that has to be removed -- set flag to true to suppress it.
|
|
||||||
if(mDataUnitID.hasTrailingStatusDibit())
|
|
||||||
{
|
|
||||||
mTrailingDibitsToSuppress = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchMessage();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -458,7 +450,7 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
|
|||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
Path directory = Paths.get("/home/denny/temp/Harris P25-2 Logs/bits");
|
Path directory = Paths.get("/home/denny/Documents/TMR/APCO25/GPS/cnyicc_cellocator");
|
||||||
// Path directory = Paths.get("/media/denny/500G1EXT4/PBITRecordings");
|
// Path directory = Paths.get("/media/denny/500G1EXT4/PBITRecordings");
|
||||||
|
|
||||||
UserPreferences userPreferences = new UserPreferences();
|
UserPreferences userPreferences = new UserPreferences();
|
||||||
@@ -479,11 +471,13 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
|
|||||||
MessageProviderModule messageProviderModule = new MessageProviderModule();
|
MessageProviderModule messageProviderModule = new MessageProviderModule();
|
||||||
processingChain.addModule(messageProviderModule);
|
processingChain.addModule(messageProviderModule);
|
||||||
|
|
||||||
|
mLog.info("Processing Directory: " + directory.toString());
|
||||||
|
|
||||||
try(OutputStream logOutput = Files.newOutputStream(directory.resolve("log.txt")))
|
try(OutputStream logOutput = Files.newOutputStream(directory.resolve("log.txt")))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*PHASE1_Hills*TRAFFIC.bits");
|
DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*.bits");
|
||||||
|
|
||||||
stream.forEach(new Consumer<Path>()
|
stream.forEach(new Consumer<Path>()
|
||||||
{
|
{
|
||||||
@@ -520,7 +514,11 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
|
|||||||
{
|
{
|
||||||
mLog.error("Error", ioe);
|
mLog.error("Error", ioe);
|
||||||
}
|
}
|
||||||
mLog.debug(message.toString());
|
|
||||||
|
if(message instanceof PacketMessage)
|
||||||
|
{
|
||||||
|
mLog.debug(message.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -264,11 +264,9 @@ public class PacketMessage extends P25Message
|
|||||||
mIdentifiers = new ArrayList<>();
|
mIdentifiers = new ArrayList<>();
|
||||||
mIdentifiers.add(getPDUSequence().getHeader().getLLID());
|
mIdentifiers.add(getPDUSequence().getHeader().getLLID());
|
||||||
|
|
||||||
if(getPacket() instanceof IPV4Packet)
|
if(getPacket() != null)
|
||||||
{
|
{
|
||||||
IPV4Packet ipv4 = (IPV4Packet)getPacket();
|
mIdentifiers.addAll(getPacket().getIdentifiers());
|
||||||
mIdentifiers.add(ipv4.getHeader().getFromAddress());
|
|
||||||
mIdentifiers.add(ipv4.getHeader().getToAddress());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public enum Protocol
|
|||||||
{
|
{
|
||||||
APCO25("APCO-25", "APCO25PHASE1", 9600),
|
APCO25("APCO-25", "APCO25PHASE1", 9600),
|
||||||
APCO25_PHASE2("APCO-25 P2", "APCO25PHASE2", 12000),
|
APCO25_PHASE2("APCO-25 P2", "APCO25PHASE2", 12000),
|
||||||
|
CELLOCATOR("CELLOCATOR", "CELLOCATOR", 0),
|
||||||
FLEETSYNC("Fleetsync", "FLEETSYNC", 1200),
|
FLEETSYNC("Fleetsync", "FLEETSYNC", 1200),
|
||||||
IPV4("IPV4", "IPV4", 0),
|
IPV4("IPV4", "IPV4", 0),
|
||||||
LOJACK("LoJack", "LOJACK", 1200),
|
LOJACK("LoJack", "LOJACK", 1200),
|
||||||
|
|||||||
@@ -157,10 +157,10 @@ public class AudioRecordingManager implements Listener<AudioSegment>
|
|||||||
}
|
}
|
||||||
|
|
||||||
audioSegment.decrementConsumerCount();
|
audioSegment.decrementConsumerCount();
|
||||||
|
|
||||||
//Grab the next one to record
|
|
||||||
audioSegment = mCompletedAudioSegmentQueue.poll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Grab the next one to record
|
||||||
|
audioSegment = mCompletedAudioSegmentQueue.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import io.github.dsheirer.sample.adapter.RealChannelShortAdapter;
|
|||||||
import io.github.dsheirer.sample.adapter.RealShortAdapter;
|
import io.github.dsheirer.sample.adapter.RealShortAdapter;
|
||||||
import io.github.dsheirer.source.config.SourceConfigMixer;
|
import io.github.dsheirer.source.config.SourceConfigMixer;
|
||||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.MixerTunerDataLine;
|
|
||||||
import io.github.dsheirer.source.tuner.MixerTunerType;
|
import io.github.dsheirer.source.tuner.MixerTunerType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -35,7 +34,6 @@ import javax.sound.sampled.Port;
|
|||||||
import javax.sound.sampled.SourceDataLine;
|
import javax.sound.sampled.SourceDataLine;
|
||||||
import javax.sound.sampled.TargetDataLine;
|
import javax.sound.sampled.TargetDataLine;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -46,7 +44,10 @@ public class MixerManager
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(MixerManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(MixerManager.class);
|
||||||
|
|
||||||
public MixerManager() {}
|
public MixerManager()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static RealMixerSource getSource(SourceConfiguration config)
|
public static RealMixerSource getSource(SourceConfiguration config)
|
||||||
{
|
{
|
||||||
@@ -193,33 +194,69 @@ public class MixerManager
|
|||||||
return outputMixers;
|
return outputMixers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<MixerTunerDataLine> getMixerTunerDataLines()
|
/**
|
||||||
|
* Obtains a target data line for the specified mixer tuner type
|
||||||
|
* @param mixerTunerType to find
|
||||||
|
* @return target data line or null.
|
||||||
|
*/
|
||||||
|
public static TargetDataLine getTunerTargetDataLine(MixerTunerType mixerTunerType)
|
||||||
{
|
{
|
||||||
List<MixerTunerDataLine> tuners = new ArrayList<>();
|
|
||||||
|
|
||||||
for(Mixer.Info mixerInfo : AudioSystem.getMixerInfo())
|
for(Mixer.Info mixerInfo : AudioSystem.getMixerInfo())
|
||||||
{
|
{
|
||||||
//Sort between the mixers and the tuner mixers, and load each
|
MixerTunerType type = MixerTunerType.getMixerTunerType(mixerInfo);
|
||||||
MixerTunerType mixerTunerType = MixerTunerType.getMixerTunerType(mixerInfo);
|
|
||||||
|
|
||||||
if(mixerTunerType != MixerTunerType.UNKNOWN)
|
if(type != null && type == mixerTunerType)
|
||||||
{
|
{
|
||||||
TargetDataLine tdl = getTargetDataLine(mixerInfo, mixerTunerType.getAudioFormat());
|
Mixer mixer = AudioSystem.getMixer(mixerInfo);
|
||||||
|
|
||||||
if(tdl != null)
|
if(mixer != null)
|
||||||
{
|
{
|
||||||
switch(mixerTunerType)
|
for(Line.Info info : mixer.getTargetLineInfo())
|
||||||
{
|
{
|
||||||
case FUNCUBE_DONGLE_PRO:
|
if(info instanceof DataLine.Info)
|
||||||
case FUNCUBE_DONGLE_PRO_PLUS:
|
{
|
||||||
tuners.add(new MixerTunerDataLine(tdl, mixerTunerType));
|
try
|
||||||
break;
|
{
|
||||||
|
Line line = mixer.getLine(info);
|
||||||
|
|
||||||
|
if(line instanceof TargetDataLine)
|
||||||
|
{
|
||||||
|
return (TargetDataLine)line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(LineUnavailableException lue)
|
||||||
|
{
|
||||||
|
mLog.error("Line Unavailable. Unable to get TargetDataLine for Mixer Tuner: " + mixerTunerType);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tuners;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the target data line supported format that most closely matches the specified audio format without
|
||||||
|
* matching sample rates.
|
||||||
|
* @param info for a data line
|
||||||
|
* @param audioFormat to find a match
|
||||||
|
* @return matching audio format or null.
|
||||||
|
*/
|
||||||
|
public static AudioFormat getMatchingFormat(DataLine.Info info, AudioFormat audioFormat)
|
||||||
|
{
|
||||||
|
for(AudioFormat audioFormatToTest: info.getFormats())
|
||||||
|
{
|
||||||
|
if(audioFormat.getSampleSizeInBits() == audioFormatToTest.getSampleSizeInBits() &&
|
||||||
|
audioFormat.getChannels() == audioFormatToTest.getChannels() &&
|
||||||
|
!(audioFormat.isBigEndian() ^ audioFormatToTest.isBigEndian()))
|
||||||
|
{
|
||||||
|
return audioFormatToTest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TargetDataLine getTargetDataLine(Mixer.Info mixerInfo, AudioFormat format)
|
private static TargetDataLine getTargetDataLine(Mixer.Info mixerInfo, AudioFormat format)
|
||||||
@@ -232,6 +269,20 @@ public class MixerManager
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
for(Line line: mixer.getTargetLines())
|
||||||
|
{
|
||||||
|
mLog.debug("Line: " + line.getLineInfo().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Line line: mixer.getSourceLines())
|
||||||
|
{
|
||||||
|
mLog.debug("Line: " + line.getLineInfo().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Mixer.Info info = mixer.getMixerInfo();
|
||||||
|
|
||||||
|
mLog.debug(info.toString());
|
||||||
|
|
||||||
DataLine.Info datalineInfo = new DataLine.Info(TargetDataLine.class, format);
|
DataLine.Info datalineInfo = new DataLine.Info(TargetDataLine.class, format);
|
||||||
|
|
||||||
retVal = (TargetDataLine)mixer.getLine(datalineInfo);
|
retVal = (TargetDataLine)mixer.getLine(datalineInfo);
|
||||||
@@ -318,6 +369,11 @@ public class MixerManager
|
|||||||
.append("\n");
|
.append("\n");
|
||||||
|
|
||||||
Mixer mixer = AudioSystem.getMixer(mixerInfo);
|
Mixer mixer = AudioSystem.getMixer(mixerInfo);
|
||||||
|
Line.Info lineInfo1 = mixer.getLineInfo();
|
||||||
|
|
||||||
|
String a = lineInfo1.toString();
|
||||||
|
|
||||||
|
Line.Info[] infos = mixer.getTargetLineInfo();
|
||||||
|
|
||||||
Line.Info[] sourceLines = mixer.getSourceLineInfo();
|
Line.Info[] sourceLines = mixer.getSourceLineInfo();
|
||||||
|
|
||||||
@@ -337,6 +393,41 @@ public class MixerManager
|
|||||||
.append("\n class:").append(lineInfo.getClass())
|
.append("\n class:").append(lineInfo.getClass())
|
||||||
.append("\n lineclass:").append(lineInfo.getLineClass())
|
.append("\n lineclass:").append(lineInfo.getLineClass())
|
||||||
.append("\n");
|
.append("\n");
|
||||||
|
|
||||||
|
if(lineInfo instanceof DataLine.Info)
|
||||||
|
{
|
||||||
|
DataLine.Info dli = (DataLine.Info)lineInfo;
|
||||||
|
|
||||||
|
for(AudioFormat format: dli.getFormats())
|
||||||
|
{
|
||||||
|
sb.append(" FORMAT:").append(format.toString()).append("\n");
|
||||||
|
|
||||||
|
if(mixerInfo.getName().startsWith("V"))
|
||||||
|
{
|
||||||
|
sb.append("Iterating formats for " + mixerInfo.getName() + " " + mixerInfo.getDescription()).append("\n");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Line line = mixer.getLine(lineInfo);
|
||||||
|
sb.append(line.getLineInfo().toString()).append("\n");
|
||||||
|
|
||||||
|
if(line instanceof TargetDataLine)
|
||||||
|
{
|
||||||
|
TargetDataLine tdl = (TargetDataLine)line;
|
||||||
|
tdl.open();
|
||||||
|
tdl.start();
|
||||||
|
byte[] bytes = new byte[1024];
|
||||||
|
int read = tdl.read(bytes, 0, bytes.length);
|
||||||
|
sb.append("READ:" + read).append("\n");
|
||||||
|
tdl.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Line.Info portInfo = new Line.Info(Port.class);
|
Line.Info portInfo = new Line.Info(Port.class);
|
||||||
@@ -389,9 +480,35 @@ public class MixerManager
|
|||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
for(MixerChannelConfiguration config : getOutputMixers())
|
MixerTunerType mixerTunerType = MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS;
|
||||||
|
TargetDataLine targetDataLine = getTunerTargetDataLine(mixerTunerType);
|
||||||
|
|
||||||
|
if(targetDataLine != null)
|
||||||
{
|
{
|
||||||
mLog.debug(config.toString());
|
try
|
||||||
|
{
|
||||||
|
targetDataLine.open(mixerTunerType.getAudioFormat());
|
||||||
|
mLog.info(targetDataLine.getFormat().toString());
|
||||||
|
targetDataLine.start();
|
||||||
|
byte[] bytes = new byte[1024];
|
||||||
|
int read = targetDataLine.read(bytes, 0, bytes.length);
|
||||||
|
|
||||||
|
while(read > 0)
|
||||||
|
{
|
||||||
|
mLog.debug("Read:" + read);
|
||||||
|
read = 0;
|
||||||
|
// read = targetDataLine.read(bytes, 0, bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetDataLine.close();
|
||||||
|
|
||||||
|
mLog.debug("Done!");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,10 +66,9 @@ public class MixerReader<T extends ReusableFloatBuffer> implements Runnable
|
|||||||
mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize();
|
mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine,
|
public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, AbstractSampleAdapter<T> abstractSampleAdapter)
|
||||||
AbstractSampleAdapter<T> abstractSampleAdapter)
|
|
||||||
{
|
{
|
||||||
this(audioFormat, targetDataLine, abstractSampleAdapter, null);
|
this(audioFormat, targetDataLine, abstractSampleAdapter, new HeartbeatManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,8 +103,12 @@ public class MixerReader<T extends ReusableFloatBuffer> implements Runnable
|
|||||||
|
|
||||||
mBuffer = new byte[mBufferSize];
|
mBuffer = new byte[mBufferSize];
|
||||||
|
|
||||||
mTargetDataLine.open(mAudioFormat);
|
mTargetDataLine.open(getAudioFormat());
|
||||||
|
mLog.info("TDL Open:" + mTargetDataLine.isOpen() + " Active:" + mTargetDataLine.isActive() + " Running:" + mTargetDataLine.isRunning());
|
||||||
|
mLog.info("Format:" + mTargetDataLine.getFormat().toString());
|
||||||
mTargetDataLine.start();
|
mTargetDataLine.start();
|
||||||
|
mLog.info("TDL Open:" + mTargetDataLine.isOpen() + " Active:" + mTargetDataLine.isActive() + " Running:" + mTargetDataLine.isRunning());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,7 +118,15 @@ public class MixerReader<T extends ReusableFloatBuffer> implements Runnable
|
|||||||
{
|
{
|
||||||
if(mTargetDataLine != null && mTargetDataLine.isOpen())
|
if(mTargetDataLine != null && mTargetDataLine.isOpen())
|
||||||
{
|
{
|
||||||
mTargetDataLine.close();
|
if(mTargetDataLine.isRunning())
|
||||||
|
{
|
||||||
|
mTargetDataLine.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mTargetDataLine.isOpen())
|
||||||
|
{
|
||||||
|
mTargetDataLine.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,9 +226,9 @@ public class MixerReader<T extends ReusableFloatBuffer> implements Runnable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Throwable t)
|
||||||
{
|
{
|
||||||
mLog.error("MixerSource - error while reading from the mixer target data line", e);
|
mLog.error("MixerSource - error while reading from the mixer target data line", t);
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* 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/>
|
|
||||||
******************************************************************************/
|
|
||||||
package io.github.dsheirer.source.tuner;
|
|
||||||
|
|
||||||
import javax.sound.sampled.TargetDataLine;
|
|
||||||
|
|
||||||
public class MixerTunerDataLine
|
|
||||||
{
|
|
||||||
private TargetDataLine mTargetDataLine;
|
|
||||||
protected MixerTunerType mMixerTunerType;
|
|
||||||
|
|
||||||
public MixerTunerDataLine( TargetDataLine line, MixerTunerType type )
|
|
||||||
{
|
|
||||||
mTargetDataLine = line;
|
|
||||||
mMixerTunerType = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TargetDataLine getTargetDataLine()
|
|
||||||
{
|
|
||||||
return mTargetDataLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MixerTunerType getMixerTunerType()
|
|
||||||
{
|
|
||||||
return mMixerTunerType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,8 +43,8 @@ import org.usb4java.DeviceDescriptor;
|
|||||||
import org.usb4java.DeviceList;
|
import org.usb4java.DeviceList;
|
||||||
import org.usb4java.LibUsb;
|
import org.usb4java.LibUsb;
|
||||||
|
|
||||||
|
import javax.sound.sampled.TargetDataLine;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -285,13 +285,11 @@ public class TunerManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TunerInitStatus initTuner(Device device,
|
private TunerInitStatus initTuner(Device device, DeviceDescriptor descriptor)
|
||||||
DeviceDescriptor descriptor)
|
|
||||||
{
|
{
|
||||||
if(device != null && descriptor != null)
|
if(device != null && descriptor != null)
|
||||||
{
|
{
|
||||||
TunerClass tunerClass = TunerClass.valueOf(descriptor.idVendor(),
|
TunerClass tunerClass = TunerClass.valueOf(descriptor.idVendor(), descriptor.idProduct());
|
||||||
descriptor.idProduct());
|
|
||||||
|
|
||||||
switch(tunerClass)
|
switch(tunerClass)
|
||||||
{
|
{
|
||||||
@@ -358,8 +356,7 @@ public class TunerManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TunerInitStatus initAirspyTuner(Device device,
|
private TunerInitStatus initAirspyTuner(Device device, DeviceDescriptor descriptor)
|
||||||
DeviceDescriptor descriptor)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -381,35 +378,30 @@ public class TunerManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TunerInitStatus initEttusB100Tuner(Device device,
|
private TunerInitStatus initEttusB100Tuner(Device device, DeviceDescriptor descriptor)
|
||||||
DeviceDescriptor descriptor)
|
|
||||||
{
|
{
|
||||||
return new TunerInitStatus(null, "Ettus B100 tuner not currently "
|
return new TunerInitStatus(null, "Ettus B100 tuner not currently supported");
|
||||||
+ "supported");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TunerInitStatus initFuncubeProTuner(Device device, DeviceDescriptor descriptor)
|
private TunerInitStatus initFuncubeProTuner(Device device, DeviceDescriptor descriptor)
|
||||||
{
|
{
|
||||||
String reason = "NOT LOADED";
|
String reason = "NOT LOADED";
|
||||||
|
|
||||||
MixerTunerDataLine dataline = getMixerTunerDataLine(TunerClass.FUNCUBE_DONGLE_PRO.getTunerType());
|
TargetDataLine tdl = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO);
|
||||||
|
|
||||||
if(dataline != null)
|
if(tdl != null)
|
||||||
{
|
{
|
||||||
FCD1TunerController controller = new FCD1TunerController(dataline, device, descriptor);
|
FCD1TunerController controller = new FCD1TunerController(tdl, device, descriptor);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
controller.init();
|
controller.init();
|
||||||
|
|
||||||
FCDTuner tuner = new FCDTuner(controller, mUserPreferences);
|
FCDTuner tuner = new FCDTuner(controller, mUserPreferences);
|
||||||
|
|
||||||
return new TunerInitStatus(tuner, "LOADED");
|
return new TunerInitStatus(tuner, "LOADED");
|
||||||
}
|
}
|
||||||
catch(SourceException e)
|
catch(SourceException e)
|
||||||
{
|
{
|
||||||
mLog.error("couldn't load funcube dongle pro tuner", e);
|
mLog.error("couldn't load funcube dongle pro tuner", e);
|
||||||
|
|
||||||
reason = "error during initialization - " + e.getLocalizedMessage();
|
reason = "error during initialization - " + e.getLocalizedMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,15 +410,14 @@ public class TunerManager
|
|||||||
reason = "couldn't find matching mixer dataline";
|
reason = "couldn't find matching mixer dataline";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not "
|
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not loaded - " + reason);
|
||||||
+ "loaded - " + reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TunerInitStatus initFuncubeProPlusTuner(Device device, DeviceDescriptor descriptor)
|
private TunerInitStatus initFuncubeProPlusTuner(Device device, DeviceDescriptor descriptor)
|
||||||
{
|
{
|
||||||
String reason = "NOT LOADED";
|
String reason = "NOT LOADED";
|
||||||
|
|
||||||
MixerTunerDataLine dataline = getMixerTunerDataLine(TunerClass.FUNCUBE_DONGLE_PRO_PLUS.getTunerType());
|
TargetDataLine dataline = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS);
|
||||||
|
|
||||||
if(dataline != null)
|
if(dataline != null)
|
||||||
{
|
{
|
||||||
@@ -435,17 +426,13 @@ public class TunerManager
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
controller.init();
|
controller.init();
|
||||||
|
|
||||||
FCDTuner tuner = new FCDTuner(controller, mUserPreferences);
|
FCDTuner tuner = new FCDTuner(controller, mUserPreferences);
|
||||||
|
|
||||||
return new TunerInitStatus(tuner, "LOADED");
|
return new TunerInitStatus(tuner, "LOADED");
|
||||||
}
|
}
|
||||||
catch(SourceException e)
|
catch(SourceException e)
|
||||||
{
|
{
|
||||||
mLog.error("couldn't load funcube dongle pro plus tuner", e);
|
mLog.error("couldn't load funcube dongle pro plus tuner", e);
|
||||||
|
reason = "error during initialization - " + e.getLocalizedMessage();
|
||||||
reason = "error during initialization - " +
|
|
||||||
e.getLocalizedMessage();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -453,8 +440,7 @@ public class TunerManager
|
|||||||
reason = "couldn't find matching mixer dataline";
|
reason = "couldn't find matching mixer dataline";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not "
|
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not loaded - " + reason);
|
||||||
+ "loaded - " + reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TunerInitStatus initHackRFTuner(Device device, DeviceDescriptor descriptor)
|
private TunerInitStatus initHackRFTuner(Device device, DeviceDescriptor descriptor)
|
||||||
@@ -473,14 +459,11 @@ public class TunerManager
|
|||||||
{
|
{
|
||||||
mLog.error("couldn't construct HackRF controller/tuner", se);
|
mLog.error("couldn't construct HackRF controller/tuner", se);
|
||||||
|
|
||||||
return new TunerInitStatus(null,
|
return new TunerInitStatus(null, "error constructing HackRF tuner controller");
|
||||||
"error constructing HackRF tuner controller");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass,
|
private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass, Device device, DeviceDescriptor deviceDescriptor)
|
||||||
Device device,
|
|
||||||
DeviceDescriptor deviceDescriptor)
|
|
||||||
{
|
{
|
||||||
String reason = "NOT LOADED";
|
String reason = "NOT LOADED";
|
||||||
|
|
||||||
@@ -540,8 +523,7 @@ public class TunerManager
|
|||||||
case RAFAELMICRO_R828D:
|
case RAFAELMICRO_R828D:
|
||||||
case UNKNOWN:
|
case UNKNOWN:
|
||||||
default:
|
default:
|
||||||
reason = "SDRTRunk doesn't currently support RTL2832 "
|
reason = "SDRTRunk doesn't currently support RTL2832 Dongle with [" + tunerType.toString() +
|
||||||
+ "Dongle with [" + tunerType.toString() +
|
|
||||||
"] tuner for tuner class[" + tunerClass.toString() + "]";
|
"] tuner for tuner class[" + tunerClass.toString() + "]";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -549,32 +531,6 @@ public class TunerManager
|
|||||||
return new TunerInitStatus(null, reason);
|
return new TunerInitStatus(null, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the first tuner mixer dataline that corresponds to the tuner class.
|
|
||||||
*
|
|
||||||
* Note: this method is not currently able to align multiple tuner mixer
|
|
||||||
* data lines of the same tuner type. If you have multiple Funcube Dongle
|
|
||||||
* tuners of the same TYPE, there is no guarantee that you will get the
|
|
||||||
* correct mixer.
|
|
||||||
*
|
|
||||||
* @param tunerClass
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private MixerTunerDataLine getMixerTunerDataLine(TunerType tunerClass)
|
|
||||||
{
|
|
||||||
Collection<MixerTunerDataLine> datalines = MixerManager.getMixerTunerDataLines();
|
|
||||||
|
|
||||||
for(MixerTunerDataLine mixerTDL : datalines)
|
|
||||||
{
|
|
||||||
if(mixerTDL.getMixerTunerType().getTunerClass() == tunerClass)
|
|
||||||
{
|
|
||||||
return mixerTDL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TunerInitStatus
|
public class TunerInitStatus
|
||||||
{
|
{
|
||||||
private Tuner mTuner;
|
private Tuner mTuner;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import io.github.dsheirer.sample.adapter.ComplexShortAdapter;
|
|||||||
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import io.github.dsheirer.source.mixer.ComplexMixer;
|
import io.github.dsheirer.source.mixer.ComplexMixer;
|
||||||
import io.github.dsheirer.source.tuner.MixerTunerDataLine;
|
import io.github.dsheirer.source.tuner.MixerTunerType;
|
||||||
import io.github.dsheirer.source.tuner.TunerClass;
|
import io.github.dsheirer.source.tuner.TunerClass;
|
||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
@@ -36,7 +36,7 @@ import org.usb4java.DeviceHandle;
|
|||||||
import org.usb4java.LibUsb;
|
import org.usb4java.LibUsb;
|
||||||
import org.usb4java.LibUsbException;
|
import org.usb4java.LibUsbException;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.TargetDataLine;
|
||||||
import javax.usb.UsbClaimException;
|
import javax.usb.UsbClaimException;
|
||||||
import javax.usb.UsbException;
|
import javax.usb.UsbException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@@ -71,8 +71,8 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
* @param minTunableFrequency
|
* @param minTunableFrequency
|
||||||
* @param maxTunableFrequency
|
* @param maxTunableFrequency
|
||||||
*/
|
*/
|
||||||
public FCDTunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDescriptor descriptor, String tunerName,
|
public FCDTunerController(MixerTunerType tunerType, TargetDataLine mixerTDL, Device device,
|
||||||
int minTunableFrequency, int maxTunableFrequency, AudioFormat audioFormat)
|
DeviceDescriptor descriptor, int minTunableFrequency, int maxTunableFrequency)
|
||||||
{
|
{
|
||||||
super(minTunableFrequency, maxTunableFrequency, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
|
super(minTunableFrequency, maxTunableFrequency, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
|
||||||
mDevice = device;
|
mDevice = device;
|
||||||
@@ -80,17 +80,17 @@ public abstract class FCDTunerController extends TunerController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mFrequencyController.setSampleRate((int)audioFormat.getSampleRate());
|
mFrequencyController.setSampleRate((int)tunerType.getAudioFormat().getSampleRate());
|
||||||
}
|
}
|
||||||
catch(SourceException se)
|
catch(SourceException se)
|
||||||
{
|
{
|
||||||
mLog.error("Error setting sample rate to [" + audioFormat.getSampleRate() + "]", se);
|
mLog.error("Error setting sample rate to [" + tunerType.getAudioFormat().getSampleRate() + "]", se);
|
||||||
}
|
}
|
||||||
|
|
||||||
mComplexMixer = new ComplexMixer( mixerTDL.getTargetDataLine(), audioFormat, tunerName,
|
mComplexMixer = new ComplexMixer( mixerTDL, tunerType.getAudioFormat(), tunerType.getDisplayString(),
|
||||||
new ComplexShortAdapter(mixerTDL.getMixerTunerType().getDisplayString()));
|
new ComplexShortAdapter(tunerType.getDisplayString()));
|
||||||
|
|
||||||
mComplexMixer.setBufferSize(audioFormat.getFrameSize() * getBufferSampleCount());
|
mComplexMixer.setBufferSize(tunerType.getAudioFormat().getFrameSize() * getBufferSampleCount());
|
||||||
|
|
||||||
mComplexMixer.setBufferListener(mReusableBufferBroadcaster);
|
mComplexMixer.setBufferListener(mReusableBufferBroadcaster);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
package io.github.dsheirer.source.tuner.fcd.proV1;
|
package io.github.dsheirer.source.tuner.fcd.proV1;
|
||||||
|
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import io.github.dsheirer.source.tuner.MixerTunerDataLine;
|
|
||||||
import io.github.dsheirer.source.tuner.MixerTunerType;
|
import io.github.dsheirer.source.tuner.MixerTunerType;
|
||||||
import io.github.dsheirer.source.tuner.TunerClass;
|
import io.github.dsheirer.source.tuner.TunerClass;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
@@ -30,6 +29,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.usb4java.Device;
|
import org.usb4java.Device;
|
||||||
import org.usb4java.DeviceDescriptor;
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
|
||||||
|
import javax.sound.sampled.TargetDataLine;
|
||||||
import javax.usb.UsbClaimException;
|
import javax.usb.UsbClaimException;
|
||||||
import javax.usb.UsbException;
|
import javax.usb.UsbException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@@ -53,10 +53,10 @@ public class FCD1TunerController extends FCDTunerController
|
|||||||
private FCD1TunerConfiguration mTunerConfiguration;
|
private FCD1TunerConfiguration mTunerConfiguration;
|
||||||
private FCD1TunerEditor mEditor;
|
private FCD1TunerEditor mEditor;
|
||||||
|
|
||||||
public FCD1TunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDescriptor descriptor)
|
public FCD1TunerController(TargetDataLine mixerTDL, Device device, DeviceDescriptor descriptor)
|
||||||
{
|
{
|
||||||
super(mixerTDL, device, descriptor, MixerTunerType.FUNCUBE_DONGLE_PRO.getDisplayString(), MINIMUM_TUNABLE_FREQUENCY,
|
super(MixerTunerType.FUNCUBE_DONGLE_PRO, mixerTDL, device, descriptor,
|
||||||
MAXIMUM_TUNABLE_FREQUENCY, MixerTunerType.FUNCUBE_DONGLE_PRO.getAudioFormat());
|
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() throws SourceException
|
public void init() throws SourceException
|
||||||
@@ -78,10 +78,7 @@ public class FCD1TunerController extends FCDTunerController
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
e.printStackTrace();
|
throw new SourceException("FCDTunerController error during construction", e);
|
||||||
|
|
||||||
throw new SourceException("FCDTunerController error " +
|
|
||||||
"during construction", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
package io.github.dsheirer.source.tuner.fcd.proplusV2;
|
package io.github.dsheirer.source.tuner.fcd.proplusV2;
|
||||||
|
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import io.github.dsheirer.source.tuner.MixerTunerDataLine;
|
|
||||||
import io.github.dsheirer.source.tuner.MixerTunerType;
|
import io.github.dsheirer.source.tuner.MixerTunerType;
|
||||||
import io.github.dsheirer.source.tuner.TunerClass;
|
import io.github.dsheirer.source.tuner.TunerClass;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
@@ -31,6 +30,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.usb4java.Device;
|
import org.usb4java.Device;
|
||||||
import org.usb4java.DeviceDescriptor;
|
import org.usb4java.DeviceDescriptor;
|
||||||
|
|
||||||
|
import javax.sound.sampled.TargetDataLine;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
@@ -42,10 +42,10 @@ public class FCD2TunerController extends FCDTunerController
|
|||||||
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
|
public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000;
|
||||||
public static final int SAMPLE_RATE = 192000;
|
public static final int SAMPLE_RATE = 192000;
|
||||||
|
|
||||||
public FCD2TunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDescriptor descriptor)
|
public FCD2TunerController(TargetDataLine mixerTDL, Device device, DeviceDescriptor descriptor)
|
||||||
{
|
{
|
||||||
super(mixerTDL, device, descriptor, MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getDisplayString(),
|
super(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS, mixerTDL, device, descriptor,
|
||||||
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY, MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat());
|
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() throws SourceException
|
public void init() throws SourceException
|
||||||
|
|||||||
Reference in New Issue
Block a user