Compare commits

...

10 Commits

Author SHA1 Message Date
Denny Sheirer
eaa3433034 #895 Cleanup and Testing of FCD Pro and Pro Plus tuners. (#896)
Co-authored-by: Denny <denny@denny-desktop>
2020-07-25 16:27:04 -04:00
Denny Sheirer
838016c89f #893 Updates UDP packet to construct a Cellocator packet payload when the source UDP port is 231 and any time the packet contains the correct start packet identifier. Updates the decode event type label. (#894)
Co-authored-by: Denny <denny@denny-desktop>
2020-07-25 09:01:44 -04:00
Denny
030a88f5a3 Updates build to 0.5.0 alpha 3 2020-07-25 08:28:27 -04:00
Denny Sheirer
de3ba5529a #890 Resolves issue where radio reference channel import only imports 1 of multiple listed control channels and adds improved detection for P25P2 simulcast sites to set the decoder type to LSM. (#892)
Co-authored-by: Denny <denny@denny-desktop>
2020-07-25 08:26:19 -04:00
Denny Sheirer
7962758e05 #866 Cellocator Packet Support (#875)
Resolves #866 Implements cellocator packet support.
2020-07-25 07:55:12 -04:00
Denny Sheirer
0f9d1c0792 #886 Resolves issue with audio recording when duplicate call detection is enabled. (#891)
Co-authored-by: Denny <denny@denny-desktop>
2020-07-25 07:24:12 -04:00
Noah Lynch
48be38a2d8 Wrap setAliases() in Platform.runLater() (#885)
Fixes issue #884 (see issue for more info).

AliasItemEditor.save() modifies objects that are observed by the
TableView's underlying SortedList.  Thus, the TableView's selection can
change by calling save(), which means the ListChangeListener can be
fired.

This is a problem for two reasons.
1. The alert dialogue that prompts the user to save a modified alias
will show when the user presses the "save" button, because saving
modifies the selected alias and causes the list to update.

2. setAliases() calls Alert.showAndWait().  From the JavaDoc:
"It must not be called during animation or layout processing."
This statement is likely there because it can cause issues like the one
here: TableView's layout processing (and the list updating) is paused
and the list is further modified (by calling save()).  Thus, when the
processing is resumed the list is in a different and unexpected state.

Calling setAliases() from Platform.runLater() will allow the SortedList,
TableView, and any other involved objects to finish processing changes
before more modifications are made.  Additionally, it allows save(),
when called by pressing the save button, to complete and set the
modifiedProperty to false, so setAliases() does not display an
additional save prompt.
2020-07-25 07:08:39 -04:00
Noah Lynch
207893e5a8 Move BinaryMessage isFull() check into try block to prevent NPE. (#881)
From issue 879:
When a BitSetFullException is caught and reset(0) is called, the
BinaryMessage is set to null.

This should resolve issue 879.
2020-07-25 06:59:13 -04:00
CurtisHx
e07d6fa854 Log build information on startup (#878) 2020-07-25 06:51:24 -04:00
Denny Sheirer
e26698d0d8 #839 Updates logging for tuner unavailable to allocate traffic channel frequencies. Logs the frequency once and then suppresses further logging. (#870)
Co-authored-by: Denny <denny@denny-desktop>
2020-06-16 05:17:05 -04:00
44 changed files with 2450 additions and 206 deletions

View File

@@ -33,7 +33,7 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.5.0-alpha2'
version = '0.5.0-alpha3'
sourceCompatibility = '13'
sourceSets {

View File

@@ -83,6 +83,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
private SourceManager mSourceManager;
private AliasModel mAliasModel;
private UserPreferences mUserPreferences;
private List<Long> mUnTunableFrequencies = new ArrayList<>();
/**
* Constructs the channel processing manager
@@ -185,15 +186,25 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
if(channel.getSourceConfiguration() instanceof SourceConfigTuner)
{
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)
{
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel
.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
{

View File

@@ -422,7 +422,7 @@ public class AliasConfigurationEditor extends SplitPane
mAliasTableView.setItems(getAliasSortedList());
mAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
mAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Alias>)c -> {
setAliases(mAliasTableView.getSelectionModel().getSelectedItems());
Platform.runLater(() -> setAliases(mAliasTableView.getSelectionModel().getSelectedItems()));
});
}

View File

@@ -287,7 +287,19 @@ public class RadioReferenceDecoder
*/
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;
}
/**

View File

@@ -479,20 +479,19 @@ public class SiteEditor extends GridPane
private void createControlChannel()
{
SiteFrequency control = null;
List<SiteFrequency> controls = new ArrayList<>();
for(SiteFrequency siteFrequency: getSiteFrequencyTableView().getItems())
{
if(isControl(siteFrequency) && siteFrequency.getFrequency() > 0.01)
{
control = siteFrequency;
continue;
controls.add(siteFrequency);
}
}
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.setHeaderText("Can't Create Channel Configuration");
alert.initOwner((getCreateChannelConfigurationButton()).getScene().getWindow());
@@ -514,9 +513,24 @@ public class SiteEditor extends GridPane
channel.setDecodeConfiguration(getDecodeConfiguration(decoderType, mCurrentSite.getSite(), mCurrentSystemInformation));
SourceConfigTuner sourceConfigTuner = new SourceConfigTuner();
sourceConfigTuner.setFrequency((long)(control.getFrequency() * 1E6));
channel.setSourceConfiguration(sourceConfigTuner);
if(controls.size() == 1)
{
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);
if(getGoToChannelEditorCheckBox().isSelected())
@@ -548,7 +562,7 @@ public class SiteEditor extends GridPane
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.setHeaderText("Can't Create Channel Configuration");
alert.initOwner((getCreateChannelConfigurationButton()).getScene().getWindow());

View File

@@ -34,7 +34,12 @@ import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
/**
* Logback and SLF4j logging implementation.
@@ -126,6 +131,17 @@ public class ApplicationLog
((ch.qos.logback.classic.Logger)logger).addAppender(mRollingFileAppender);
// 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("*******************************************************************");
@@ -163,4 +179,25 @@ public class ApplicationLog
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;
}
}

View File

@@ -20,6 +20,9 @@
package io.github.dsheirer.module.decode.ip;
import io.github.dsheirer.identifier.Identifier;
import java.util.List;
/**
* Data packet interface
*/
@@ -28,16 +31,21 @@ public interface IPacket
/**
* Header for the data packet.
*/
public IHeader getHeader();
IHeader getHeader();
/**
* Optional payload for the packet
* @return null if there is no payload
*/
public IPacket getPayload();
IPacket getPayload();
/**
* 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();
}

View File

@@ -22,6 +22,8 @@ package io.github.dsheirer.module.decode.ip;
import io.github.dsheirer.bits.BinaryMessage;
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.IPV4Packet;
import io.github.dsheirer.module.decode.ip.udp.UDPPacket;
@@ -82,15 +84,23 @@ public class PacketMessageFactory
/**
* 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 binaryMessage containing the IP/UDP payload
* @param offset in the message to the start of the payload
* @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)
{
case 231:
//Cellocator
if(MCGPHeader.isCellocatorMessage(binaryMessage, offset))
{
return MCGPMessageFactory.create(binaryMessage, offset);
}
break;
case 4001: //Location Service
break;
case 4004: //XCMP Service
@@ -103,6 +113,23 @@ public class PacketMessageFactory
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);
}
}

View File

@@ -21,7 +21,10 @@
package io.github.dsheirer.module.decode.ip;
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 java.util.Collections;
import java.util.List;
public class UnknownCompressedHeaderPacket implements IPacket
{
@@ -87,4 +90,10 @@ public class UnknownCompressedHeaderPacket implements IPacket
{
return false;
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -21,6 +21,9 @@
package io.github.dsheirer.module.decode.ip;
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
{
@@ -78,4 +81,10 @@ public class UnknownPacket implements IPacket
{
return false;
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -21,8 +21,11 @@
package io.github.dsheirer.module.decode.ip.ars;
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;
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.
return null;
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -21,10 +21,13 @@
package io.github.dsheirer.module.decode.ip.ipv4;
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 io.github.dsheirer.module.decode.ip.PacketMessageFactory;
import io.github.dsheirer.module.decode.ip.UnknownPacket;
import java.util.ArrayList;
import java.util.List;
/**
* IPV4 packet
@@ -33,6 +36,7 @@ public class IPV4Packet extends Packet
{
private IPV4Header mHeader;
private IPacket mPayload;
private List<Identifier> mIdentifiers;
/**
* Constructs an IPV4 packet parser
@@ -109,4 +113,23 @@ public class IPV4Packet extends Packet
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;
}
}

View File

@@ -21,11 +21,15 @@
package io.github.dsheirer.module.decode.ip.udp;
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 io.github.dsheirer.module.decode.ip.PacketMessageFactory;
import io.github.dsheirer.module.decode.ip.UnknownPacket;
import java.util.Collections;
import java.util.List;
public class UDPPacket extends Packet
{
private UDPHeader mHeader;
@@ -73,16 +77,31 @@ public class UDPPacket extends Packet
@Override
public IPacket getPayload()
{
if(mPayload == null && getHeader().isValid())
if(mPayload == null)
{
mPayload = PacketMessageFactory.createUDPPayload(getHeader().getDestinationPort(), getMessage(), getOffset() + getHeader().getLength());
}
else
{
//Set the offset to the message offset plus 64 bits for UDP header
mPayload = new UnknownPacket(getMessage(), getOffset() + 64);
if(getHeader().isValid())
{
mPayload = PacketMessageFactory.createUDPPayload(getHeader().getSourcePort(),
getHeader().getDestinationPort(), getMessage(), getOffset() + getHeader().getLength());
}
else
{
//Set the offset to the message offset plus 64 bits for UDP header
mPayload = new UnknownPacket(getMessage(), getOffset() + 64);
}
}
return mPayload;
}
@Override
public List<Identifier> getIdentifiers()
{
if(hasPayload())
{
return getPayload().getIdentifiers();
}
return Collections.emptyList();
}
}

View File

@@ -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.ip.IPacket;
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.udp.UDPPacket;
import io.github.dsheirer.module.decode.p25.P25DecodeEvent;
@@ -935,6 +936,10 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
ARSPacket arsPacket = (ARSPacket)udpPayload;
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
for(Identifier identifier: packet.getIdentifiers())
{
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
.channel(getCurrentChannel())
@@ -945,9 +950,32 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
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
{
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
for(Identifier identifier: packet.getIdentifiers())
{
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
.channel(getCurrentChannel())

View File

@@ -25,7 +25,6 @@ import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
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.bits.BitSetFullException;
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.ISyncDetectListener;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.IMessageProvider;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.message.MessageProviderModule;
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.decode.DecoderType;
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.P25MessageFactory;
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.packet.PacketMessage;
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.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.protocol.Protocol;
import io.github.dsheirer.record.AudioRecordingManager;
import io.github.dsheirer.record.binary.BinaryReader;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.buffer.ReusableByteBuffer;
import org.slf4j.Logger;
@@ -201,6 +193,17 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
{
mBinaryMessage.add(dibit.getBit1());
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)
{
@@ -209,17 +212,6 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
//Reset so that we can start over again
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
{
@@ -458,7 +450,7 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
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");
UserPreferences userPreferences = new UserPreferences();
@@ -479,11 +471,13 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
MessageProviderModule messageProviderModule = new MessageProviderModule();
processingChain.addModule(messageProviderModule);
mLog.info("Processing Directory: " + directory.toString());
try(OutputStream logOutput = Files.newOutputStream(directory.resolve("log.txt")))
{
try
{
DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*PHASE1_Hills*TRAFFIC.bits");
DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*.bits");
stream.forEach(new Consumer<Path>()
{
@@ -520,7 +514,11 @@ public class P25P1MessageFramer implements Listener<Dibit>, IP25P1DataUnitDetect
{
mLog.error("Error", ioe);
}
mLog.debug(message.toString());
if(message instanceof PacketMessage)
{
mLog.debug(message.toString());
}
}
}
});

View File

@@ -264,11 +264,9 @@ public class PacketMessage extends P25Message
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getPDUSequence().getHeader().getLLID());
if(getPacket() instanceof IPV4Packet)
if(getPacket() != null)
{
IPV4Packet ipv4 = (IPV4Packet)getPacket();
mIdentifiers.add(ipv4.getHeader().getFromAddress());
mIdentifiers.add(ipv4.getHeader().getToAddress());
mIdentifiers.addAll(getPacket().getIdentifiers());
}
}

View File

@@ -30,6 +30,7 @@ public enum Protocol
{
APCO25("APCO-25", "APCO25PHASE1", 9600),
APCO25_PHASE2("APCO-25 P2", "APCO25PHASE2", 12000),
CELLOCATOR("CELLOCATOR", "CELLOCATOR", 0),
FLEETSYNC("Fleetsync", "FLEETSYNC", 1200),
IPV4("IPV4", "IPV4", 0),
LOJACK("LoJack", "LOJACK", 1200),

View File

@@ -157,10 +157,10 @@ public class AudioRecordingManager implements Listener<AudioSegment>
}
audioSegment.decrementConsumerCount();
//Grab the next one to record
audioSegment = mCompletedAudioSegmentQueue.poll();
}
//Grab the next one to record
audioSegment = mCompletedAudioSegmentQueue.poll();
}
}

View File

@@ -20,7 +20,6 @@ import io.github.dsheirer.sample.adapter.RealChannelShortAdapter;
import io.github.dsheirer.sample.adapter.RealShortAdapter;
import io.github.dsheirer.source.config.SourceConfigMixer;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.MixerTunerDataLine;
import io.github.dsheirer.source.tuner.MixerTunerType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,7 +34,6 @@ import javax.sound.sampled.Port;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
@@ -46,7 +44,10 @@ public class MixerManager
{
private final static Logger mLog = LoggerFactory.getLogger(MixerManager.class);
public MixerManager() {}
public MixerManager()
{
}
public static RealMixerSource getSource(SourceConfiguration config)
{
@@ -193,33 +194,69 @@ public class MixerManager
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())
{
//Sort between the mixers and the tuner mixers, and load each
MixerTunerType mixerTunerType = MixerTunerType.getMixerTunerType(mixerInfo);
MixerTunerType type = 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:
case FUNCUBE_DONGLE_PRO_PLUS:
tuners.add(new MixerTunerDataLine(tdl, mixerTunerType));
break;
if(info instanceof DataLine.Info)
{
try
{
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)
@@ -232,6 +269,20 @@ public class MixerManager
{
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);
retVal = (TargetDataLine)mixer.getLine(datalineInfo);
@@ -318,6 +369,11 @@ public class MixerManager
.append("\n");
Mixer mixer = AudioSystem.getMixer(mixerInfo);
Line.Info lineInfo1 = mixer.getLineInfo();
String a = lineInfo1.toString();
Line.Info[] infos = mixer.getTargetLineInfo();
Line.Info[] sourceLines = mixer.getSourceLineInfo();
@@ -337,6 +393,41 @@ public class MixerManager
.append("\n class:").append(lineInfo.getClass())
.append("\n lineclass:").append(lineInfo.getLineClass())
.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);
@@ -389,9 +480,35 @@ public class MixerManager
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();
}
}
}
}

View File

@@ -66,10 +66,9 @@ public class MixerReader<T extends ReusableFloatBuffer> implements Runnable
mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize();
}
public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine,
AbstractSampleAdapter<T> abstractSampleAdapter)
public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, 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];
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();
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())
{
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();
}
}

View File

@@ -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;
}
}

View File

@@ -43,8 +43,8 @@ import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceList;
import org.usb4java.LibUsb;
import javax.sound.sampled.TargetDataLine;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -285,13 +285,11 @@ public class TunerManager
}
}
private TunerInitStatus initTuner(Device device,
DeviceDescriptor descriptor)
private TunerInitStatus initTuner(Device device, DeviceDescriptor descriptor)
{
if(device != null && descriptor != null)
{
TunerClass tunerClass = TunerClass.valueOf(descriptor.idVendor(),
descriptor.idProduct());
TunerClass tunerClass = TunerClass.valueOf(descriptor.idVendor(), descriptor.idProduct());
switch(tunerClass)
{
@@ -358,8 +356,7 @@ public class TunerManager
}
}
private TunerInitStatus initAirspyTuner(Device device,
DeviceDescriptor descriptor)
private TunerInitStatus initAirspyTuner(Device device, DeviceDescriptor descriptor)
{
try
{
@@ -381,35 +378,30 @@ public class TunerManager
}
private TunerInitStatus initEttusB100Tuner(Device device,
DeviceDescriptor descriptor)
private TunerInitStatus initEttusB100Tuner(Device device, DeviceDescriptor descriptor)
{
return new TunerInitStatus(null, "Ettus B100 tuner not currently "
+ "supported");
return new TunerInitStatus(null, "Ettus B100 tuner not currently supported");
}
private TunerInitStatus initFuncubeProTuner(Device device, DeviceDescriptor descriptor)
{
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
{
controller.init();
FCDTuner tuner = new FCDTuner(controller, mUserPreferences);
return new TunerInitStatus(tuner, "LOADED");
}
catch(SourceException e)
{
mLog.error("couldn't load funcube dongle pro tuner", e);
reason = "error during initialization - " + e.getLocalizedMessage();
}
}
@@ -418,15 +410,14 @@ public class TunerManager
reason = "couldn't find matching mixer dataline";
}
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not "
+ "loaded - " + reason);
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not loaded - " + reason);
}
private TunerInitStatus initFuncubeProPlusTuner(Device device, DeviceDescriptor descriptor)
{
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)
{
@@ -435,17 +426,13 @@ public class TunerManager
try
{
controller.init();
FCDTuner tuner = new FCDTuner(controller, mUserPreferences);
return new TunerInitStatus(tuner, "LOADED");
}
catch(SourceException 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
@@ -453,8 +440,7 @@ public class TunerManager
reason = "couldn't find matching mixer dataline";
}
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not "
+ "loaded - " + reason);
return new TunerInitStatus(null, "Funcube Dongle Pro tuner not loaded - " + reason);
}
private TunerInitStatus initHackRFTuner(Device device, DeviceDescriptor descriptor)
@@ -473,14 +459,11 @@ public class TunerManager
{
mLog.error("couldn't construct HackRF controller/tuner", se);
return new TunerInitStatus(null,
"error constructing HackRF tuner controller");
return new TunerInitStatus(null, "error constructing HackRF tuner controller");
}
}
private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass,
Device device,
DeviceDescriptor deviceDescriptor)
private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass, Device device, DeviceDescriptor deviceDescriptor)
{
String reason = "NOT LOADED";
@@ -540,8 +523,7 @@ public class TunerManager
case RAFAELMICRO_R828D:
case UNKNOWN:
default:
reason = "SDRTRunk doesn't currently support RTL2832 "
+ "Dongle with [" + tunerType.toString() +
reason = "SDRTRunk doesn't currently support RTL2832 Dongle with [" + tunerType.toString() +
"] tuner for tuner class[" + tunerClass.toString() + "]";
break;
}
@@ -549,32 +531,6 @@ public class TunerManager
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
{
private Tuner mTuner;

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.sample.adapter.ComplexShortAdapter;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
import io.github.dsheirer.source.SourceException;
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.TunerController;
import io.github.dsheirer.source.tuner.TunerType;
@@ -36,7 +36,7 @@ import org.usb4java.DeviceHandle;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.TargetDataLine;
import javax.usb.UsbClaimException;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
@@ -71,8 +71,8 @@ public abstract class FCDTunerController extends TunerController
* @param minTunableFrequency
* @param maxTunableFrequency
*/
public FCDTunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDescriptor descriptor, String tunerName,
int minTunableFrequency, int maxTunableFrequency, AudioFormat audioFormat)
public FCDTunerController(MixerTunerType tunerType, TargetDataLine mixerTDL, Device device,
DeviceDescriptor descriptor, int minTunableFrequency, int maxTunableFrequency)
{
super(minTunableFrequency, maxTunableFrequency, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT);
mDevice = device;
@@ -80,17 +80,17 @@ public abstract class FCDTunerController extends TunerController
try
{
mFrequencyController.setSampleRate((int)audioFormat.getSampleRate());
mFrequencyController.setSampleRate((int)tunerType.getAudioFormat().getSampleRate());
}
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,
new ComplexShortAdapter(mixerTDL.getMixerTunerType().getDisplayString()));
mComplexMixer = new ComplexMixer( mixerTDL, tunerType.getAudioFormat(), tunerType.getDisplayString(),
new ComplexShortAdapter(tunerType.getDisplayString()));
mComplexMixer.setBufferSize(audioFormat.getFrameSize() * getBufferSampleCount());
mComplexMixer.setBufferSize(tunerType.getAudioFormat().getFrameSize() * getBufferSampleCount());
mComplexMixer.setBufferListener(mReusableBufferBroadcaster);
}

View File

@@ -18,7 +18,6 @@
package io.github.dsheirer.source.tuner.fcd.proV1;
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.TunerClass;
import io.github.dsheirer.source.tuner.TunerType;
@@ -30,6 +29,7 @@ import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import javax.sound.sampled.TargetDataLine;
import javax.usb.UsbClaimException;
import javax.usb.UsbException;
import java.nio.ByteBuffer;
@@ -53,10 +53,10 @@ public class FCD1TunerController extends FCDTunerController
private FCD1TunerConfiguration mTunerConfiguration;
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,
MAXIMUM_TUNABLE_FREQUENCY, MixerTunerType.FUNCUBE_DONGLE_PRO.getAudioFormat());
super(MixerTunerType.FUNCUBE_DONGLE_PRO, mixerTDL, device, descriptor,
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
}
public void init() throws SourceException
@@ -78,10 +78,7 @@ public class FCD1TunerController extends FCDTunerController
}
catch(Exception e)
{
e.printStackTrace();
throw new SourceException("FCDTunerController error " +
"during construction", e);
throw new SourceException("FCDTunerController error during construction", e);
}
}

View File

@@ -19,7 +19,6 @@
package io.github.dsheirer.source.tuner.fcd.proplusV2;
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.TunerClass;
import io.github.dsheirer.source.tuner.TunerType;
@@ -31,6 +30,7 @@ import org.slf4j.LoggerFactory;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import javax.sound.sampled.TargetDataLine;
import java.nio.ByteBuffer;
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 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(),
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY, MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat());
super(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS, mixerTDL, device, descriptor,
MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY);
}
public void init() throws SourceException