Compare commits

...

19 Commits

Author SHA1 Message Date
Denny Sheirer
22ffb656be #1464 Reduces logging when SDRplay API is not installed on local machine. (#1465)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-20 12:10:17 -05:00
Denny Sheirer
de80d2a6c1 #1459 Updates gradle build to fix bug related to building releases for each OS (#1463)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-20 12:04:48 -05:00
Denny Sheirer
a320b4a24f #1461 Updates expected sdrplay library path for OSX (#1462)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-20 04:55:50 -05:00
Denny Sheirer
fc13777211 #1456 Resolves issue with shutdown when no sdrplay api is available. Version update to 0.6.0 alpha-2 (#1457)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-19 10:18:19 -05:00
Denny Sheirer
75347f9d0a #52 SDRPlay RSP tuner support. (#1453)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-19 08:21:54 -05:00
Denny Sheirer
9abf64c6ab #1449 Updates version to 0.5.1 final (#1451)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-19 07:17:10 -05:00
Denny Sheirer
1702103f5d #1449 Updates version to 0.5.1 beta-2 (#1450)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-19 07:10:44 -05:00
Denny Sheirer
8f68a989b9 #1444 Adds: (#1447)
* DMR Recording viewer utility
* DMR talker aliases
* DMR SMS messages
* DMR Hytera Tier-III compressed talkgroups support
* DMR UDT packet sequences
* DMR Hytera Aloha and other unknown CSBK and Link Control messages

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-18 08:36:55 -05:00
Denny Sheirer
b51a0102ef #1422 Resolves issue where MotoTRBO IP Site Connect and Capacity Plus voice group user message not decoding Radio & Talkgroup IDs correctly. Creates standalone application for viewing DMR messages from recordings. (#1442)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-02-11 15:32:41 -05:00
Denny Sheirer
e177c2c6b0 #1426 Adds JRE accessibility module to support assistive technologies. (#1427)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2023-01-21 07:21:00 -05:00
Denny Sheirer
3567fdf51d #1393 Updates DMR Talkgroup and Radio ID editors to use correct formatting. Patch submitted by @hockeyref2 (#1425)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2023-01-20 06:50:18 -05:00
Denny Sheirer
323097ba65 #1423 Updates Icecast/Broadcastify feed to attempt reconnect following a mountpoint in use error response. (#1424)
Co-authored-by: Denny Sheirer <sheirerd@ainfosec.com>
2023-01-20 05:39:38 -05:00
Denny Sheirer
33f2bab7ac #1419 Reduce logging from USBTunerController for (temporary) failed transfer buffer resubmits. (#1420)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-18 04:38:30 -05:00
Denny Sheirer
c063cfb201 #1416 Updates P25-P1 To Expose ICMP & Unknown Packet Events in Event History View (#1417)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-16 05:31:27 -05:00
Denny Sheirer
e99aa3d3b4 #1411 Updates version to 0.5.0 beta1 (#1412)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 06:36:22 -05:00
Denny Sheirer
ec162ff84c #1408 Updates USB tuners to enhance transfer buffer error handling and logging. When an error occurs while (re)submitting a transfer buffer to LibUsb to fill with sample data, the buffer is temporarily placed in an error queue. Upon successfully submitting the next transfer buffer, we attempt to resubmit a buffer from the error queue, so that we wait until the device can accept buffers before trying to resubmit. If all transfer buffers are exhausted because they are in the error queue, then the tuner gets shutdown with an error state ... there's really no way to recover at this point. (#1409)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 06:16:37 -05:00
Denny Sheirer
f0db2865f2 #1397 Resolves issue where decoding channels go IDLE after period of time. Isolated the cause to a multi-threading issue where the polyphase channel manager receives notification that the tuner center frequency has changed and sets a flag to update each of the channels. This happens concurrent with a previous tuner center frequency change where the flag gets set and the second thread is updating the output processors with the previous frequency change and then unsets the frequency change flag, completely missing the second/latest frequency change update and causing the channels to all tune-out their desired signals as the tuner center frequency changes and the output channels continue mixing to the previous frequency. (#1407)
Identified and resolved issue where a 2-stream polyphase synthesizer was being updated with an incorrectly designed synthesis filter which was causing massive droop on the second half of the reconstructed channel.  Oddly enough, this didn't seem to impact the P25 decoders, but it may have impacted other decoding.

Identified and resolved issue with concurrent modification of the modules list in the processing chain associated with recent addition of the channel FFT view in the channel power tab.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-15 04:12:42 -05:00
Denny Sheirer
f97b2150b9 #1397 Adds channel fft frequency overlay, cursor readout, pll tracking indicator, and channel bandwidth lines. (#1406)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-13 04:28:00 -05:00
Denny Sheirer
8ce8aaddf9 #1399 Resolves device ID parsing issue for Motorola ARS Device Registration. (#1400)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-01-06 04:17:57 -05:00
434 changed files with 37517 additions and 1353 deletions

View File

@@ -22,10 +22,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
- name: Set up JDK 19
uses: actions/setup-java@v2
with:
java-version: '17'
java-version: '19'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Build with Gradle

2
.idea/misc.xml generated
View File

@@ -4,7 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_19_PREVIEW" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,36 @@
Auto-generating Java code from SDRPlay API using jextract.
Note: the api headers in the 3.11 folder are equivalent to the 3.08 headers. The only
difference from the 3.07 headers is the addition of the boolean 'valid' flag to DeviceT structure.
1. Download JDK19 compatible build of jextract JDK: https://jdk.java.net/jextract/
2. If generating on windows platform, modify the sdrplay_api.h to remove the windows
pieces of the file. Specifically:
2a. Comment out these lines but leave the *HANDLE typedef:
//#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
//#elif defined(STATIC_LIB) || defined(__GNUC__)
//#define _SDRPLAY_DLL_QUALIFIER
//#endif
//#endif // _SDRPLAY_DLL_QUALIFIER
2b. Remove the '_SDRPLAY_DLL_QUALIFIER' from in front of the method declarations
2c. Change the 3.11 version string to 3.08 either in the header file, or in the generated java class
3. Open command prompt to the root source code where the generated files should be placed:
..\SDRtrunk\sdrplay-api\src\main\java
4. Generate the code:
c:\Users\Denny\Downloads\jdk-19-ea2-panama\bin\jextract "C:\Users\Denny\git\sdrtrunk\sdrplay-api\api\v3_11\sdrplay_api.h" -l libsdrplay_api --source
5. Do this for all versions of the API.

View File

@@ -0,0 +1,229 @@
#ifndef SDRPLAY_API_H
#define SDRPLAY_API_H
#include "sdrplay_api_dev.h"
#include "sdrplay_api_rx_channel.h"
#include "sdrplay_api_callback.h"
//#if defined(_M_X64) || defined(_M_IX86)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
//#elif defined(STATIC_LIB) || defined(__GNUC__)
//#define _SDRPLAY_DLL_QUALIFIER
//#endif
//#endif // _SDRPLAY_DLL_QUALIFIER
// Application code should check that it is compiled against the same API version
// sdrplay_api_ApiVersion() returns the API version
#define SDRPLAY_API_VERSION (float)(3.07)
// API Constants
#define SDRPLAY_MAX_DEVICES (16)
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
#define SDRPLAY_MAX_SER_NO_LEN (64)
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
#define SDRPLAY_RSP1_ID (1)
#define SDRPLAY_RSP1A_ID (255)
#define SDRPLAY_RSP2_ID (2)
#define SDRPLAY_RSPduo_ID (3)
#define SDRPLAY_RSPdx_ID (4)
// Enum types
typedef enum
{
sdrplay_api_Success = 0,
sdrplay_api_Fail = 1,
sdrplay_api_InvalidParam = 2,
sdrplay_api_OutOfRange = 3,
sdrplay_api_GainUpdateError = 4,
sdrplay_api_RfUpdateError = 5,
sdrplay_api_FsUpdateError = 6,
sdrplay_api_HwError = 7,
sdrplay_api_AliasingError = 8,
sdrplay_api_AlreadyInitialised = 9,
sdrplay_api_NotInitialised = 10,
sdrplay_api_NotEnabled = 11,
sdrplay_api_HwVerError = 12,
sdrplay_api_OutOfMemError = 13,
sdrplay_api_ServiceNotResponding = 14,
sdrplay_api_StartPending = 15,
sdrplay_api_StopPending = 16,
sdrplay_api_InvalidMode = 17,
sdrplay_api_FailedVerification1 = 18,
sdrplay_api_FailedVerification2 = 19,
sdrplay_api_FailedVerification3 = 20,
sdrplay_api_FailedVerification4 = 21,
sdrplay_api_FailedVerification5 = 22,
sdrplay_api_FailedVerification6 = 23,
sdrplay_api_InvalidServiceVersion = 24
} sdrplay_api_ErrT;
typedef enum
{
sdrplay_api_Update_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_Dev_Fs = 0x00000001,
sdrplay_api_Update_Dev_Ppm = 0x00000002,
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
// Reasons for master and slave mode
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
sdrplay_api_Update_Tuner_Gr = 0x00008000,
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
sdrplay_api_Update_Tuner_Frf = 0x00020000,
sdrplay_api_Update_Tuner_BwType = 0x00040000,
sdrplay_api_Update_Tuner_IfType = 0x00080000,
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
} sdrplay_api_ReasonForUpdateT;
typedef enum
{
sdrplay_api_Update_Ext1_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
// Reasons for master and slave mode
} sdrplay_api_ReasonForUpdateExtension1T;
typedef enum
{
sdrplay_api_DbgLvl_Disable = 0,
sdrplay_api_DbgLvl_Verbose = 1,
sdrplay_api_DbgLvl_Warning = 2,
sdrplay_api_DbgLvl_Error = 3,
sdrplay_api_DbgLvl_Message = 4,
} sdrplay_api_DbgLvl_t;
// Device structure
typedef struct
{
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
unsigned char hwVer;
sdrplay_api_TunerSelectT tuner;
sdrplay_api_RspDuoModeT rspDuoMode;
double rspDuoSampleFreq;
HANDLE dev;
} sdrplay_api_DeviceT;
// Device parameter structure
typedef struct
{
sdrplay_api_DevParamsT *devParams;
sdrplay_api_RxChannelParamsT *rxChannelA;
sdrplay_api_RxChannelParamsT *rxChannelB;
} sdrplay_api_DeviceParamsT;
// Extended error message structure
typedef struct
{
char file[256];
char function[256];
int line;
char message[1024];
} sdrplay_api_ErrorInfoT;
// Comman API function types
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
// Device API function types
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// API function definitions
#ifdef __cplusplus
extern "C"
{
#endif
// Comman API function definitions
sdrplay_api_ErrT sdrplay_api_Open(void);
sdrplay_api_ErrT sdrplay_api_Close(void);
sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
// Device API function definitions
sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
#ifdef __cplusplus
}
#endif
#endif //SDRPLAY_API_H

View File

@@ -0,0 +1,78 @@
#ifndef SDRPLAY_API_CALLBACK_H
#define SDRPLAY_API_CALLBACK_H
// Event callback enums
typedef enum
{
sdrplay_api_Overload_Detected = 0,
sdrplay_api_Overload_Corrected = 1,
} sdrplay_api_PowerOverloadCbEventIdT;
typedef enum
{
sdrplay_api_MasterInitialised = 0,
sdrplay_api_SlaveAttached = 1,
sdrplay_api_SlaveDetached = 2,
sdrplay_api_SlaveInitialised = 3,
sdrplay_api_SlaveUninitialised = 4,
sdrplay_api_MasterDllDisappeared = 5,
sdrplay_api_SlaveDllDisappeared = 6,
} sdrplay_api_RspDuoModeCbEventIdT;
typedef enum
{
sdrplay_api_GainChange = 0,
sdrplay_api_PowerOverloadChange = 1,
sdrplay_api_DeviceRemoved = 2,
sdrplay_api_RspDuoModeChange = 3,
} sdrplay_api_EventT;
// Event callback parameter structs
typedef struct
{
unsigned int gRdB;
unsigned int lnaGRdB;
double currGain;
} sdrplay_api_GainCbParamT;
typedef struct
{
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
} sdrplay_api_PowerOverloadCbParamT;
typedef struct
{
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
} sdrplay_api_RspDuoModeCbParamT;
// Event parameters overlay
typedef union
{
sdrplay_api_GainCbParamT gainParams;
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
} sdrplay_api_EventParamsT;
// Stream callback parameter structs
typedef struct
{
unsigned int firstSampleNum;
int grChanged;
int rfChanged;
int fsChanged;
unsigned int numSamples;
} sdrplay_api_StreamCbParamsT;
// Callback function prototypes
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
// Callback function struct
typedef struct
{
sdrplay_api_StreamCallback_t StreamACbFn;
sdrplay_api_StreamCallback_t StreamBCbFn;
sdrplay_api_EventCallback_t EventCbFn;
} sdrplay_api_CallbackFnsT;
#endif //SDRPLAY_API_CALLBACK_H

View File

@@ -0,0 +1,55 @@
#ifndef SDRPLAY_API_CONTROL_H
#define SDRPLAY_API_CONTROL_H
// Control parameter enums
typedef enum
{
sdrplay_api_AGC_DISABLE = 0,
sdrplay_api_AGC_100HZ = 1,
sdrplay_api_AGC_50HZ = 2,
sdrplay_api_AGC_5HZ = 3,
sdrplay_api_AGC_CTRL_EN = 4
} sdrplay_api_AgcControlT;
typedef enum
{
sdrplay_api_ADSB_DECIMATION = 0,
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
} sdrplay_api_AdsbModeT;
// Control parameter structs
typedef struct
{
unsigned char DCenable; // default: 1
unsigned char IQenable; // default: 1
} sdrplay_api_DcOffsetT;
typedef struct
{
unsigned char enable; // default: 0
unsigned char decimationFactor; // default: 1
unsigned char wideBandSignal; // default: 0
} sdrplay_api_DecimationT;
typedef struct
{
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
int setPoint_dBfs; // default: -60
unsigned short attack_ms; // default: 0
unsigned short decay_ms; // default: 0
unsigned short decay_delay_ms; // default: 0
unsigned short decay_threshold_dB; // default: 0
int syncUpdate; // default: 0
} sdrplay_api_AgcT;
typedef struct
{
sdrplay_api_DcOffsetT dcOffset;
sdrplay_api_DecimationT decimation;
sdrplay_api_AgcT agc;
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
} sdrplay_api_ControlParamsT;
#endif //SDRPLAY_API_CONTROL_H

View File

@@ -0,0 +1,51 @@
#ifndef SDRPLAY_API_DEV_H
#define SDRPLAY_API_DEV_H
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
// Dev parameter enums
typedef enum
{
sdrplay_api_ISOCH = 0,
sdrplay_api_BULK = 1
} sdrplay_api_TransferModeT;
// Dev parameter structs
typedef struct
{
double fsHz; // default: 2000000.0
unsigned char syncUpdate; // default: 0
unsigned char reCal; // default: 0
} sdrplay_api_FsFreqT;
typedef struct
{
unsigned int sampleNum; // default: 0
unsigned int period; // default: 0
} sdrplay_api_SyncUpdateT;
typedef struct
{
unsigned char resetGainUpdate; // default: 0
unsigned char resetRfUpdate; // default: 0
unsigned char resetFsUpdate; // default: 0
} sdrplay_api_ResetFlagsT;
typedef struct
{
double ppm; // default: 0.0
sdrplay_api_FsFreqT fsFreq;
sdrplay_api_SyncUpdateT syncUpdate;
sdrplay_api_ResetFlagsT resetFlags;
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
unsigned int samplesPerPkt; // default: 0 (output param)
sdrplay_api_Rsp1aParamsT rsp1aParams;
sdrplay_api_Rsp2ParamsT rsp2Params;
sdrplay_api_RspDuoParamsT rspDuoParams;
sdrplay_api_RspDxParamsT rspDxParams;
} sdrplay_api_DevParamsT;
#endif //SDRPLAY_API_DEV_H

View File

@@ -0,0 +1,22 @@
#ifndef SDRPLAY_API_RSP1A_H
#define SDRPLAY_API_RSP1A_H
#define RSPIA_NUM_LNA_STATES 10
#define RSPIA_NUM_LNA_STATES_AM 7
#define RSPIA_NUM_LNA_STATES_LBAND 9
// RSP1A parameter enums
// RSP1A parameter structs
typedef struct
{
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_Rsp1aParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
} sdrplay_api_Rsp1aTunerParamsT;
#endif //SDRPLAY_API_RSP1A_H

View File

@@ -0,0 +1,35 @@
#ifndef SDRPLAY_API_RSP2_H
#define SDRPLAY_API_RSP2_H
#define RSPII_NUM_LNA_STATES 9
#define RSPII_NUM_LNA_STATES_AMPORT 5
#define RSPII_NUM_LNA_STATES_420MHZ 6
// RSP2 parameter enums
typedef enum
{
sdrplay_api_Rsp2_ANTENNA_A = 5,
sdrplay_api_Rsp2_ANTENNA_B = 6,
} sdrplay_api_Rsp2_AntennaSelectT;
typedef enum
{
sdrplay_api_Rsp2_AMPORT_1 = 1,
sdrplay_api_Rsp2_AMPORT_2 = 0,
} sdrplay_api_Rsp2_AmPortSelectT;
// RSP2 parameter structs
typedef struct
{
unsigned char extRefOutputEn; // default: 0
} sdrplay_api_Rsp2ParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
} sdrplay_api_Rsp2TunerParamsT;
#endif //SDRPLAY_API_RSP2_H

View File

@@ -0,0 +1,40 @@
#ifndef SDRPLAY_API_RSPduo_H
#define SDRPLAY_API_RSPduo_H
#define RSPDUO_NUM_LNA_STATES 10
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
#define RSPDUO_NUM_LNA_STATES_AM 7
#define RSPDUO_NUM_LNA_STATES_LBAND 9
// RSPduo parameter enums
typedef enum
{
sdrplay_api_RspDuoMode_Unknown = 0,
sdrplay_api_RspDuoMode_Single_Tuner = 1,
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
sdrplay_api_RspDuoMode_Master = 4,
sdrplay_api_RspDuoMode_Slave = 8,
} sdrplay_api_RspDuoModeT;
typedef enum
{
sdrplay_api_RspDuo_AMPORT_1 = 1,
sdrplay_api_RspDuo_AMPORT_2 = 0,
} sdrplay_api_RspDuo_AmPortSelectT;
// RSPduo parameter structs
typedef struct
{
int extRefOutputEn; // default: 0
} sdrplay_api_RspDuoParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
unsigned char tuner1AmNotchEnable; // default: 0
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDuoTunerParamsT;
#endif //SDRPLAY_API_RSPduo_H

View File

@@ -0,0 +1,45 @@
#ifndef SDRPLAY_API_RSPDX_H
#define SDRPLAY_API_RSPDX_H
#include "sdrplay_api_tuner.h"
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_60 20 // Number of LNA states when using AM Port 2 between 12 and 60MHz
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
#define RSPDX_NUM_LNA_STATES_DX 26 // Number of LNA states in DX path
// RSPdx parameter enums
typedef enum
{
sdrplay_api_RspDx_ANTENNA_A = 0,
sdrplay_api_RspDx_ANTENNA_B = 1,
sdrplay_api_RspDx_ANTENNA_C = 2,
} sdrplay_api_RspDx_AntennaSelectT;
typedef enum
{
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
} sdrplay_api_RspDx_HdrModeBwT;
// RSPdx parameter structs
typedef struct
{
unsigned char hdrEnable; // default: 0
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDxParamsT;
typedef struct
{
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
} sdrplay_api_RspDxTunerParamsT;
#endif //SDRPLAY_API_RSPDX_H

View File

@@ -0,0 +1,21 @@
#ifndef SDRPLAY_API_RX_CHANNEL_H
#define SDRPLAY_API_RX_CHANNEL_H
#include "sdrplay_api_tuner.h"
#include "sdrplay_api_control.h"
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
typedef struct
{
sdrplay_api_TunerParamsT tunerParams;
sdrplay_api_ControlParamsT ctrlParams;
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
} sdrplay_api_RxChannelParamsT;
#endif //SDRPLAY_API_RX_CHANNEL_H

View File

@@ -0,0 +1,93 @@
#ifndef SDRPLAY_API_TUNER_H
#define SDRPLAY_API_TUNER_H
#define MAX_BB_GR (59)
// Tuner parameter enums
typedef enum
{
sdrplay_api_BW_Undefined = 0,
sdrplay_api_BW_0_200 = 200,
sdrplay_api_BW_0_300 = 300,
sdrplay_api_BW_0_600 = 600,
sdrplay_api_BW_1_536 = 1536,
sdrplay_api_BW_5_000 = 5000,
sdrplay_api_BW_6_000 = 6000,
sdrplay_api_BW_7_000 = 7000,
sdrplay_api_BW_8_000 = 8000
} sdrplay_api_Bw_MHzT;
typedef enum
{
sdrplay_api_IF_Undefined = -1,
sdrplay_api_IF_Zero = 0,
sdrplay_api_IF_0_450 = 450,
sdrplay_api_IF_1_620 = 1620,
sdrplay_api_IF_2_048 = 2048
} sdrplay_api_If_kHzT;
typedef enum
{
sdrplay_api_LO_Undefined = 0,
sdrplay_api_LO_Auto = 1,
sdrplay_api_LO_120MHz = 2,
sdrplay_api_LO_144MHz = 3,
sdrplay_api_LO_168MHz = 4
} sdrplay_api_LoModeT;
typedef enum
{
sdrplay_api_EXTENDED_MIN_GR = 0,
sdrplay_api_NORMAL_MIN_GR = 20
} sdrplay_api_MinGainReductionT;
typedef enum
{
sdrplay_api_Tuner_Neither = 0,
sdrplay_api_Tuner_A = 1,
sdrplay_api_Tuner_B = 2,
sdrplay_api_Tuner_Both = 3,
} sdrplay_api_TunerSelectT;
// Tuner parameter structs
typedef struct
{
float curr;
float max;
float min;
} sdrplay_api_GainValuesT;
typedef struct
{
int gRdB; // default: 50
unsigned char LNAstate; // default: 0
unsigned char syncUpdate; // default: 0
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
sdrplay_api_GainValuesT gainVals; // output parameter
} sdrplay_api_GainT;
typedef struct
{
double rfHz; // default: 200000000.0
unsigned char syncUpdate; // default: 0
} sdrplay_api_RfFreqT;
typedef struct
{
unsigned char dcCal; // default: 3 (Periodic mode)
unsigned char speedUp; // default: 0 (No speedup)
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
} sdrplay_api_DcOffsetTunerT;
typedef struct
{
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
sdrplay_api_GainT gain;
sdrplay_api_RfFreqT rfFreq;
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
} sdrplay_api_TunerParamsT;
#endif //SDRPLAY_API_TUNER_H

View File

@@ -0,0 +1,230 @@
#ifndef SDRPLAY_API_H
#define SDRPLAY_API_H
#include "sdrplay_api_dev.h"
#include "sdrplay_api_rx_channel.h"
#include "sdrplay_api_callback.h"
//#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
//#elif defined(STATIC_LIB) || defined(__GNUC__)
//#define _SDRPLAY_DLL_QUALIFIER
//#endif
//#endif // _SDRPLAY_DLL_QUALIFIER
// Application code should check that it is compiled against the same API version
// sdrplay_api_ApiVersion() returns the API version
#define SDRPLAY_API_VERSION (float)(3.11)
// API Constants
#define SDRPLAY_MAX_DEVICES (16)
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
#define SDRPLAY_MAX_SER_NO_LEN (64)
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
#define SDRPLAY_RSP1_ID (1)
#define SDRPLAY_RSP1A_ID (255)
#define SDRPLAY_RSP2_ID (2)
#define SDRPLAY_RSPduo_ID (3)
#define SDRPLAY_RSPdx_ID (4)
// Enum types
typedef enum
{
sdrplay_api_Success = 0,
sdrplay_api_Fail = 1,
sdrplay_api_InvalidParam = 2,
sdrplay_api_OutOfRange = 3,
sdrplay_api_GainUpdateError = 4,
sdrplay_api_RfUpdateError = 5,
sdrplay_api_FsUpdateError = 6,
sdrplay_api_HwError = 7,
sdrplay_api_AliasingError = 8,
sdrplay_api_AlreadyInitialised = 9,
sdrplay_api_NotInitialised = 10,
sdrplay_api_NotEnabled = 11,
sdrplay_api_HwVerError = 12,
sdrplay_api_OutOfMemError = 13,
sdrplay_api_ServiceNotResponding = 14,
sdrplay_api_StartPending = 15,
sdrplay_api_StopPending = 16,
sdrplay_api_InvalidMode = 17,
sdrplay_api_FailedVerification1 = 18,
sdrplay_api_FailedVerification2 = 19,
sdrplay_api_FailedVerification3 = 20,
sdrplay_api_FailedVerification4 = 21,
sdrplay_api_FailedVerification5 = 22,
sdrplay_api_FailedVerification6 = 23,
sdrplay_api_InvalidServiceVersion = 24
} sdrplay_api_ErrT;
typedef enum
{
sdrplay_api_Update_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_Dev_Fs = 0x00000001,
sdrplay_api_Update_Dev_Ppm = 0x00000002,
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
// Reasons for master and slave mode
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
sdrplay_api_Update_Tuner_Gr = 0x00008000,
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
sdrplay_api_Update_Tuner_Frf = 0x00020000,
sdrplay_api_Update_Tuner_BwType = 0x00040000,
sdrplay_api_Update_Tuner_IfType = 0x00080000,
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
} sdrplay_api_ReasonForUpdateT;
typedef enum
{
sdrplay_api_Update_Ext1_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
// Reasons for master and slave mode
} sdrplay_api_ReasonForUpdateExtension1T;
typedef enum
{
sdrplay_api_DbgLvl_Disable = 0,
sdrplay_api_DbgLvl_Verbose = 1,
sdrplay_api_DbgLvl_Warning = 2,
sdrplay_api_DbgLvl_Error = 3,
sdrplay_api_DbgLvl_Message = 4,
} sdrplay_api_DbgLvl_t;
// Device structure
typedef struct
{
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
unsigned char hwVer;
sdrplay_api_TunerSelectT tuner;
sdrplay_api_RspDuoModeT rspDuoMode;
unsigned char valid;
double rspDuoSampleFreq;
HANDLE dev;
} sdrplay_api_DeviceT;
// Device parameter structure
typedef struct
{
sdrplay_api_DevParamsT *devParams;
sdrplay_api_RxChannelParamsT *rxChannelA;
sdrplay_api_RxChannelParamsT *rxChannelB;
} sdrplay_api_DeviceParamsT;
// Extended error message structure
typedef struct
{
char file[256];
char function[256];
int line;
char message[1024];
} sdrplay_api_ErrorInfoT;
// Comman API function types
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
// Device API function types
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate, double newSampleRate);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// API function definitions
#ifdef __cplusplus
extern "C"
{
#endif
// Comman API function definitions
sdrplay_api_ErrT sdrplay_api_Open(void);
sdrplay_api_ErrT sdrplay_api_Close(void);
sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
// Device API function definitions
sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
#ifdef __cplusplus
}
#endif
#endif //SDRPLAY_API_H

View File

@@ -0,0 +1,79 @@
#ifndef SDRPLAY_API_CALLBACK_H
#define SDRPLAY_API_CALLBACK_H
// Event callback enums
typedef enum
{
sdrplay_api_Overload_Detected = 0,
sdrplay_api_Overload_Corrected = 1,
} sdrplay_api_PowerOverloadCbEventIdT;
typedef enum
{
sdrplay_api_MasterInitialised = 0,
sdrplay_api_SlaveAttached = 1,
sdrplay_api_SlaveDetached = 2,
sdrplay_api_SlaveInitialised = 3,
sdrplay_api_SlaveUninitialised = 4,
sdrplay_api_MasterDllDisappeared = 5,
sdrplay_api_SlaveDllDisappeared = 6,
} sdrplay_api_RspDuoModeCbEventIdT;
typedef enum
{
sdrplay_api_GainChange = 0,
sdrplay_api_PowerOverloadChange = 1,
sdrplay_api_DeviceRemoved = 2,
sdrplay_api_RspDuoModeChange = 3,
sdrplay_api_DeviceFailure = 4,
} sdrplay_api_EventT;
// Event callback parameter structs
typedef struct
{
unsigned int gRdB;
unsigned int lnaGRdB;
double currGain;
} sdrplay_api_GainCbParamT;
typedef struct
{
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
} sdrplay_api_PowerOverloadCbParamT;
typedef struct
{
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
} sdrplay_api_RspDuoModeCbParamT;
// Event parameters overlay
typedef union
{
sdrplay_api_GainCbParamT gainParams;
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
} sdrplay_api_EventParamsT;
// Stream callback parameter structs
typedef struct
{
unsigned int firstSampleNum;
int grChanged;
int rfChanged;
int fsChanged;
unsigned int numSamples;
} sdrplay_api_StreamCbParamsT;
// Callback function prototypes
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
// Callback function struct
typedef struct
{
sdrplay_api_StreamCallback_t StreamACbFn;
sdrplay_api_StreamCallback_t StreamBCbFn;
sdrplay_api_EventCallback_t EventCbFn;
} sdrplay_api_CallbackFnsT;
#endif //SDRPLAY_API_CALLBACK_H

View File

@@ -0,0 +1,55 @@
#ifndef SDRPLAY_API_CONTROL_H
#define SDRPLAY_API_CONTROL_H
// Control parameter enums
typedef enum
{
sdrplay_api_AGC_DISABLE = 0,
sdrplay_api_AGC_100HZ = 1,
sdrplay_api_AGC_50HZ = 2,
sdrplay_api_AGC_5HZ = 3,
sdrplay_api_AGC_CTRL_EN = 4
} sdrplay_api_AgcControlT;
typedef enum
{
sdrplay_api_ADSB_DECIMATION = 0,
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
} sdrplay_api_AdsbModeT;
// Control parameter structs
typedef struct
{
unsigned char DCenable; // default: 1
unsigned char IQenable; // default: 1
} sdrplay_api_DcOffsetT;
typedef struct
{
unsigned char enable; // default: 0
unsigned char decimationFactor; // default: 1
unsigned char wideBandSignal; // default: 0
} sdrplay_api_DecimationT;
typedef struct
{
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
int setPoint_dBfs; // default: -60
unsigned short attack_ms; // default: 0
unsigned short decay_ms; // default: 0
unsigned short decay_delay_ms; // default: 0
unsigned short decay_threshold_dB; // default: 0
int syncUpdate; // default: 0
} sdrplay_api_AgcT;
typedef struct
{
sdrplay_api_DcOffsetT dcOffset;
sdrplay_api_DecimationT decimation;
sdrplay_api_AgcT agc;
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
} sdrplay_api_ControlParamsT;
#endif //SDRPLAY_API_CONTROL_H

View File

@@ -0,0 +1,51 @@
#ifndef SDRPLAY_API_DEV_H
#define SDRPLAY_API_DEV_H
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
// Dev parameter enums
typedef enum
{
sdrplay_api_ISOCH = 0,
sdrplay_api_BULK = 1
} sdrplay_api_TransferModeT;
// Dev parameter structs
typedef struct
{
double fsHz; // default: 2000000.0
unsigned char syncUpdate; // default: 0
unsigned char reCal; // default: 0
} sdrplay_api_FsFreqT;
typedef struct
{
unsigned int sampleNum; // default: 0
unsigned int period; // default: 0
} sdrplay_api_SyncUpdateT;
typedef struct
{
unsigned char resetGainUpdate; // default: 0
unsigned char resetRfUpdate; // default: 0
unsigned char resetFsUpdate; // default: 0
} sdrplay_api_ResetFlagsT;
typedef struct
{
double ppm; // default: 0.0
sdrplay_api_FsFreqT fsFreq;
sdrplay_api_SyncUpdateT syncUpdate;
sdrplay_api_ResetFlagsT resetFlags;
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
unsigned int samplesPerPkt; // default: 0 (output param)
sdrplay_api_Rsp1aParamsT rsp1aParams;
sdrplay_api_Rsp2ParamsT rsp2Params;
sdrplay_api_RspDuoParamsT rspDuoParams;
sdrplay_api_RspDxParamsT rspDxParams;
} sdrplay_api_DevParamsT;
#endif //SDRPLAY_API_DEV_H

View File

@@ -0,0 +1,22 @@
#ifndef SDRPLAY_API_RSP1A_H
#define SDRPLAY_API_RSP1A_H
#define RSPIA_NUM_LNA_STATES 10
#define RSPIA_NUM_LNA_STATES_AM 7
#define RSPIA_NUM_LNA_STATES_LBAND 9
// RSP1A parameter enums
// RSP1A parameter structs
typedef struct
{
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_Rsp1aParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
} sdrplay_api_Rsp1aTunerParamsT;
#endif //SDRPLAY_API_RSP1A_H

View File

@@ -0,0 +1,35 @@
#ifndef SDRPLAY_API_RSP2_H
#define SDRPLAY_API_RSP2_H
#define RSPII_NUM_LNA_STATES 9
#define RSPII_NUM_LNA_STATES_AMPORT 5
#define RSPII_NUM_LNA_STATES_420MHZ 6
// RSP2 parameter enums
typedef enum
{
sdrplay_api_Rsp2_ANTENNA_A = 5,
sdrplay_api_Rsp2_ANTENNA_B = 6,
} sdrplay_api_Rsp2_AntennaSelectT;
typedef enum
{
sdrplay_api_Rsp2_AMPORT_1 = 1,
sdrplay_api_Rsp2_AMPORT_2 = 0,
} sdrplay_api_Rsp2_AmPortSelectT;
// RSP2 parameter structs
typedef struct
{
unsigned char extRefOutputEn; // default: 0
} sdrplay_api_Rsp2ParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
} sdrplay_api_Rsp2TunerParamsT;
#endif //SDRPLAY_API_RSP2_H

View File

@@ -0,0 +1,40 @@
#ifndef SDRPLAY_API_RSPduo_H
#define SDRPLAY_API_RSPduo_H
#define RSPDUO_NUM_LNA_STATES 10
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
#define RSPDUO_NUM_LNA_STATES_AM 7
#define RSPDUO_NUM_LNA_STATES_LBAND 9
// RSPduo parameter enums
typedef enum
{
sdrplay_api_RspDuoMode_Unknown = 0,
sdrplay_api_RspDuoMode_Single_Tuner = 1,
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
sdrplay_api_RspDuoMode_Master = 4,
sdrplay_api_RspDuoMode_Slave = 8,
} sdrplay_api_RspDuoModeT;
typedef enum
{
sdrplay_api_RspDuo_AMPORT_1 = 1,
sdrplay_api_RspDuo_AMPORT_2 = 0,
} sdrplay_api_RspDuo_AmPortSelectT;
// RSPduo parameter structs
typedef struct
{
int extRefOutputEn; // default: 0
} sdrplay_api_RspDuoParamsT;
typedef struct
{
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
unsigned char tuner1AmNotchEnable; // default: 0
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDuoTunerParamsT;
#endif //SDRPLAY_API_RSPduo_H

View File

@@ -0,0 +1,46 @@
#ifndef SDRPLAY_API_RSPDX_H
#define SDRPLAY_API_RSPDX_H
#include "sdrplay_api_tuner.h"
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_50 20 // Number of LNA states when using AM Port 2 between 12 and 50MHz
#define RSPDX_NUM_LNA_STATES_AMPORT2_50_60 25 // Number of LNA states when using AM Port 2 between 50 and 60MHz
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
#define RSPDX_NUM_LNA_STATES_DX 22 // Number of LNA states in DX path
// RSPdx parameter enums
typedef enum
{
sdrplay_api_RspDx_ANTENNA_A = 0,
sdrplay_api_RspDx_ANTENNA_B = 1,
sdrplay_api_RspDx_ANTENNA_C = 2,
} sdrplay_api_RspDx_AntennaSelectT;
typedef enum
{
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
} sdrplay_api_RspDx_HdrModeBwT;
// RSPdx parameter structs
typedef struct
{
unsigned char hdrEnable; // default: 0
unsigned char biasTEnable; // default: 0
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
unsigned char rfNotchEnable; // default: 0
unsigned char rfDabNotchEnable; // default: 0
} sdrplay_api_RspDxParamsT;
typedef struct
{
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
} sdrplay_api_RspDxTunerParamsT;
#endif //SDRPLAY_API_RSPDX_H

View File

@@ -0,0 +1,21 @@
#ifndef SDRPLAY_API_RX_CHANNEL_H
#define SDRPLAY_API_RX_CHANNEL_H
#include "sdrplay_api_tuner.h"
#include "sdrplay_api_control.h"
#include "sdrplay_api_rsp1a.h"
#include "sdrplay_api_rsp2.h"
#include "sdrplay_api_rspDuo.h"
#include "sdrplay_api_rspDx.h"
typedef struct
{
sdrplay_api_TunerParamsT tunerParams;
sdrplay_api_ControlParamsT ctrlParams;
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
} sdrplay_api_RxChannelParamsT;
#endif //SDRPLAY_API_RX_CHANNEL_H

View File

@@ -0,0 +1,93 @@
#ifndef SDRPLAY_API_TUNER_H
#define SDRPLAY_API_TUNER_H
#define MAX_BB_GR (59)
// Tuner parameter enums
typedef enum
{
sdrplay_api_BW_Undefined = 0,
sdrplay_api_BW_0_200 = 200,
sdrplay_api_BW_0_300 = 300,
sdrplay_api_BW_0_600 = 600,
sdrplay_api_BW_1_536 = 1536,
sdrplay_api_BW_5_000 = 5000,
sdrplay_api_BW_6_000 = 6000,
sdrplay_api_BW_7_000 = 7000,
sdrplay_api_BW_8_000 = 8000
} sdrplay_api_Bw_MHzT;
typedef enum
{
sdrplay_api_IF_Undefined = -1,
sdrplay_api_IF_Zero = 0,
sdrplay_api_IF_0_450 = 450,
sdrplay_api_IF_1_620 = 1620,
sdrplay_api_IF_2_048 = 2048
} sdrplay_api_If_kHzT;
typedef enum
{
sdrplay_api_LO_Undefined = 0,
sdrplay_api_LO_Auto = 1,
sdrplay_api_LO_120MHz = 2,
sdrplay_api_LO_144MHz = 3,
sdrplay_api_LO_168MHz = 4
} sdrplay_api_LoModeT;
typedef enum
{
sdrplay_api_EXTENDED_MIN_GR = 0,
sdrplay_api_NORMAL_MIN_GR = 20
} sdrplay_api_MinGainReductionT;
typedef enum
{
sdrplay_api_Tuner_Neither = 0,
sdrplay_api_Tuner_A = 1,
sdrplay_api_Tuner_B = 2,
sdrplay_api_Tuner_Both = 3,
} sdrplay_api_TunerSelectT;
// Tuner parameter structs
typedef struct
{
float curr;
float max;
float min;
} sdrplay_api_GainValuesT;
typedef struct
{
int gRdB; // default: 50
unsigned char LNAstate; // default: 0
unsigned char syncUpdate; // default: 0
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
sdrplay_api_GainValuesT gainVals; // output parameter
} sdrplay_api_GainT;
typedef struct
{
double rfHz; // default: 200000000.0
unsigned char syncUpdate; // default: 0
} sdrplay_api_RfFreqT;
typedef struct
{
unsigned char dcCal; // default: 3 (Periodic mode)
unsigned char speedUp; // default: 0 (No speedup)
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
} sdrplay_api_DcOffsetTunerT;
typedef struct
{
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
sdrplay_api_GainT gain;
sdrplay_api_RfFreqT rfFreq;
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
} sdrplay_api_TunerParamsT;
#endif //SDRPLAY_API_TUNER_H

View File

@@ -1,6 +1,8 @@
import java.text.SimpleDateFormat
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,6 +18,29 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
/**
* Instructions for building/compiling the sdrtrunk application.
*
* Prerequisites:
* Install and configure an OpenJDK version 19+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
* - Optional: install and configure Gradle 7.6+
*
* Scenario 1: run the application via gradle command line from the source code root directory:
* command: ./gradlew run
*
* Scenario 2: create an application release for the current operating system
* command: ./gradlew runtimeZipCurrent
* Note: release image is located in the /build/image/ directory
*
* Scenario 3: create releases for Linux and OSX operating systems using downloaded JDKs
* command: ./gradlew runtimeZipOthers
* Note: release images are located in the /build/image/ directory
*
* Scenario 4: create release for Windows operating system using downloaded JDK
* command: ./gradlew runtimeZipWindows
* Note: release image is located in the /build/image/ directory
*/
plugins {
id 'application'
id 'java'
@@ -28,8 +53,14 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.5.0'
sourceCompatibility = '17'
version = '0.6.0-alpha3'
//Java 19 is required for this version of the Project Panama preview/incubator feature
java {
toolchain {
languageVersion = JavaLanguageVersion.of(19)
}
}
sourceSets {
main.java.srcDirs 'src/main'
@@ -90,19 +121,46 @@ dependencies {
implementation 'pl.edu.icm:JLargeArrays:1.6'
}
def os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem
/**
* This is needed for the JDK17 vector API ... until it moves out of incubation
* This is needed for the JDK19 panama and vector API ... until it moves out of incubation or preview
*/
tasks.withType(JavaCompile) {
options.compilerArgs.add("--enable-preview") //Panama Foreign Function/Memory is preview in JDK 19
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
}
/**
* JVM Arguments for running the application on Windows.
*/
def jvmArgsWindows =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', //windows users for JIDE
'--add-modules=jdk.incubator.vector', //Needed while Project Panama API is still in incubator
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
'--enable-native-access=ALL-UNNAMED',
'-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64'] //Allows Project Panama Foreign Function access to system library
/**
* JVM Arguments for running the application on Linux.
*/
def jvmArgsLinux =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-modules=jdk.incubator.vector', //Needed while Project Panama API is still in incubator
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
'--enable-native-access=ALL-UNNAMED',
'-Djava.library.path=/usr/local/lib'] //Allows Project Panama Foreign Function access to system library
application {
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
applicationDefaultJvmArgs =
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
if(os.isWindows()) {
applicationDefaultJvmArgs = jvmArgsWindows
}
else {
applicationDefaultJvmArgs = jvmArgsLinux
}
}
jar {
@@ -111,7 +169,7 @@ jar {
'Implementation-Title' : 'sdrtrunk project',
'Implementation-Version' : archiveVersion,
'Created-By' : "Gradle ${gradle.gradleVersion}",
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-Timestamp' : new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
)
@@ -145,82 +203,142 @@ String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
/**
* Download URLs to download the JDK as part of the gradle build packaging process
*/
String jdk_download_base = 'https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-'
String jdk_download_suffix = '-full.tar.gz'
String jdk_download_linux_aarch64 = jdk_download_base + 'linux-aarch64' + jdk_download_suffix
String jdk_download_linux_x86_64 = jdk_download_base + 'linux-amd64' + jdk_download_suffix
String jdk_download_osx_x86_64 = jdk_download_base + 'macos-aarch64' + jdk_download_suffix
String jdk_download_osx_aarch64 = jdk_download_base + 'macos-amd64' + jdk_download_suffix
String jdk_download_windows_x86_64 = jdk_download_base + 'windows-amd64-full.zip'
/**
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
* including this option: '-PdownloadJdks=true'
* Configures the runtime zip task with additional options/settings.
* @param rt task to configure
* @param jvmArgs to use (windows or linux)
* @return configured task.
*/
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
/**
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
*
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
*
* >: gradle runtimeZip
*
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
*
* >: gradle runtimeZip -PdownloadJdks=true
*/
runtime {
//Build by automatically downloading JDKs for the OS and CPU targets
if(downloadJdks) {
println("Building all runtime images")
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
targetPlatform(targetLinuxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
}
targetPlatform(targetLinuxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
}
targetPlatform(targetOsxAarch64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
}
targetPlatform(targetOsxX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
}
targetPlatform(targetWindowsX86_64) {
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
}
}
//Build using pre-downloaded JDKs
else if(hasTargetJdks) {
println("Building all runtime images using pre-downloaded JDKs")
if(file(jdk_linux_aarch64).exists()) {
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
}
if(file(jdk_linux_x86_64).exists()) {
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
}
if(file(jdk_osx_aarch64).exists()) {
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
}
if(file(jdk_osx_x86_64).exists()) {
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
}
if(file(jdk_windows_x86_64).exists()) {
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
}
}
//Build using locally installed JDK only
else {
println("Building a single runtime image using locally installed JDK")
}
def configure(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
//Use the correct set of JVM arguments
rt.extension.launcherData.get().jvmArgs = jvmArgs
//Additional modules to include with auto-detected modules.
modules = ['jdk.crypto.ec'] //jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
//jdk.accessibility is used with assistive technologies like screen readers
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility')
//Use auto-detected modules and 'add' any specified modules.
additive = true
rt.extension.additive.set(true)
//Recommended options for reducing/shrinking runtime target image size
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
rt.extension.addOptions('--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages')
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
file("$buildDir/image/sdr-trunk-" + version + ".zip")
rt.extension.imageZip.set(file("$buildDir/image/sdr-trunk.zip"))
}
/**
* Creates a release package for the current operating system.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipCurrent
* Usage: gradlew runtimeZipCurrent (windows - using gradlew)
* Usage: ./gradlew runtimeZipCurrent (linux - using gradlew)
*/
tasks.register('runtimeZipCurrent', org.beryx.runtime.RuntimeZipTask) {rt ->
def arch = System.properties.getProperty("os.arch")
def javaHome = org.gradle.internal.jvm.Jvm.current().getJavaHome().toString()
if(os.isWindows()) {
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
configure(rt, jvmArgsWindows)
}
else if(os.isLinux())
{
if(arch.equals("amd64")) {
rt.extension.targetPlatform(targetLinuxX86_64, javaHome)
}
else if(arch.equals("aarch64")) {
rt.extension.targetPlatform(targetLinuxAarch64, javaHome)
}
configure(rt, jvmArgsLinux)
}
else if(os.isMacOsX())
{
if(arch.equals("amd64")) {
rt.extension.targetPlatform(targetOsxX86_64, javaHome)
}
else if(arch.equals("aarch64")) {
rt.extension.targetPlatform(targetOsxAarch64, javaHome)
}
configure(rt, jvmArgsLinux)
}
}
/**
* Creates a Windows release package.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipWindows
* Usage: gradlew runtimeZipWindows (windows - using gradlew)
* Usage: ./gradlew runtimeZipWindows (linux - using gradlew)
*/
tasks.register('runtimeZipWindows', org.beryx.runtime.RuntimeZipTask) {rt ->
if(file(jdk_windows_x86_64).exists()) {
rt.extension.targetPlatform(targetWindowsX86_64, jdk_windows_x86_64, [])
}
else {
rt.extension.targetPlatform(targetWindowsX86_64, jdkDownload(jdk_download_windows_x86_64))
}
configure(rt, jvmArgsWindows)
}
/**
* Creates Linux & OSX release packages.
*
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
*
* Usage: gradle runtimeZipOthers
* Usage: gradlew runtimeZipOthers (windows - using gradlew)
* Usage: ./gradlew runtimeZipOthers (linux - using gradlew)
*/
tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
//Linux aarch64
if(file(jdk_linux_aarch64).exists()) {
rt.extension.targetPlatform(targetLinuxAarch64, jdk_linux_aarch64, [])
}
else {
rt.extension.targetPlatform(targetLinuxAarch64, jdkDownload(jdk_download_linux_aarch64))
}
//Linux x86-64
if(file(jdk_linux_x86_64).exists()) {
rt.extension.targetPlatform(targetLinuxX86_64, jdk_linux_x86_64, [])
}
else {
rt.extension.targetPlatform(targetLinuxX86_64, jdkDownload(jdk_download_linux_x86_64))
}
//OSX aarch64
if(file(jdk_osx_aarch64).exists()) {
rt.extension.targetPlatform(targetOsxAarch64, jdk_osx_aarch64, [])
}
else {
rt.extension.targetPlatform(targetOsxAarch64, jdkDownload(jdk_download_osx_aarch64))
}
//Linux x86-64
if(file(jdk_osx_x86_64).exists()) {
rt.extension.targetPlatform(targetOsxX86_64, jdk_osx_x86_64, [])
}
else {
rt.extension.targetPlatform(targetOsxX86_64, jdkDownload(jdk_download_osx_x86_64))
}
configure(rt, jvmArgsLinux);
}

View File

@@ -68,7 +68,7 @@ public enum BroadcastState
/**
* Specified mount point is already in use
*/
MOUNT_POINT_IN_USE("Mount Point In Use", true),
MOUNT_POINT_IN_USE("Mount Point In Use", false),
/**
* Network is unavailable or the server address cannot be resolved

View File

@@ -1,35 +1,38 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* 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.bits;
import io.github.dsheirer.edac.CRC;
import java.io.UnsupportedEncodingException;
import java.util.BitSet;
import org.apache.commons.lang3.Validate;
import org.apache.commons.math3.util.FastMath;
import java.util.BitSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BinaryMessage extends BitSet
{
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = LoggerFactory.getLogger(BinaryMessage.class);
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final String UTF_8 = "UTF-8";
/**
* Logical (ie constructed) size of this bitset, despite the actual size of
@@ -1038,6 +1041,51 @@ public class BinaryMessage extends BitSet
return buffer;
}
/**
* Loads the hexadecimal text into a binary message
* @param hex text to load from
* @return loaded binary message
*/
public static BinaryMessage loadHex(String hex)
{
if(!hex.matches("[0-9A-F]*"))
{
throw new IllegalArgumentException("Message must contain only 0-9 and A-F hexadecimal characters");
}
int length = hex.length() / 2;
if(length * 2 < hex.length())
{
length++;
}
BinaryMessage buffer = new BinaryMessage(length * 8);
int bufferOffset = 0;
for(int x = 0; x < hex.length(); x += 2)
{
int endIndex = x + 2;
if(endIndex > hex.length())
{
endIndex--;
}
String rawHex = hex.substring(x, endIndex);
if(rawHex.length() == 1)
{
rawHex = rawHex + "0";
}
int value = Integer.parseInt(rawHex, 16);
buffer.setByte(bufferOffset, (byte)(value & 0xFF));
bufferOffset += 8;
}
return buffer;
}
/**
* Left rotates the bits between start and end indices, number of places.
*/
@@ -1133,19 +1181,101 @@ public class BinaryMessage extends BitSet
this.xor(mask);
}
public static void main(String[] args)
/**
* Parse ISO-7 bit characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
*/
public String parseISO7(int offset, int characterCount)
{
BinaryMessage message = new BinaryMessage(10);
message.set(1);
message.set(4);
message.set(5);
message.set(8);
message.set(9);
byte[] bytes = new byte[characterCount];
System.out.println("Hex: " + message.toHexString() + " Size:" + message.size());
for(int x = 0; x < 10; x++)
for(int x = 0; x < characterCount; x++)
{
System.out.println(x + ": " + message.getHex(x, x + 10));
bytes[x] = getByte(CHARACTER_7_BIT, x * 7 + offset);
}
return new String(bytes);
}
/**
* Parse ISO-8 bit characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
*/
public String parseISO8(int offset, int characterCount)
{
byte[] bytes = new byte[characterCount];
for(int x = 0; x < characterCount; x++)
{
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
}
return new String(bytes);
}
/**
* Parse UTF-8 characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
* @throws UnsupportedEncodingException if UTF-8 encoding is not supported.
*/
public String parseUTF8(int offset, int characterCount) throws UnsupportedEncodingException
{
byte[] bytes = new byte[characterCount];
for(int x = 0; x < characterCount; x++)
{
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
}
return new String(bytes, UTF_8);
}
/**
* Parse 16-bit unicode characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
*/
public String parseUnicode(int offset, int characterCount)
{
int length = characterCount * 2;
byte[] bytes = new byte[length];
for(int x = 0; x < length; x++)
{
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
}
return new String(bytes);
}
/**
* Parse 4-bit Binary Coded Decimal (BCD) characters from the message starting at the offset.
* @param offset where to start parsing.
* @param characterCount number of characters to parse
* @return parsed message.
*/
public String parseBCD4(int offset, int characterCount)
{
StringBuilder sb = new StringBuilder();
int count = 0;
while(count < characterCount)
{
int nibble = getNibble(offset);
//This will parse digits 0-9 and anything higher will parse as hexadecimal A-F
sb.append(Integer.toHexString(nibble).toUpperCase());
offset += 4;
count++;
}
return sb.toString();
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* 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.channel.metadata;
@@ -28,10 +27,11 @@ import io.github.dsheirer.module.decode.event.DecodeEventPanel;
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import java.awt.Color;
import net.miginfocom.swing.MigLayout;
import javax.swing.JPanel;
import java.awt.Color;
public class NowPlayingPanel extends JPanel
{
@@ -45,13 +45,14 @@ public class NowPlayingPanel extends JPanel
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
* messages, events, and spectral view.
*/
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences)
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences,
SettingsManager settingsManager)
{
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
mChannelPowerPanel = new ChannelPowerPanel(playlistManager);
mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager);
init();
}
@@ -68,6 +69,10 @@ public class NowPlayingPanel extends JPanel
tabbedPane.setFont(this.getFont());
tabbedPane.setForeground(Color.BLACK);
//Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing
tabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(tabbedPane.getSelectedIndex() == tabbedPane
.indexOfComponent(mChannelPowerPanel)));
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
splitPane.setShowGripper(true);
splitPane.add(mChannelMetadataPanel);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,8 @@ import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import io.github.dsheirer.source.tuner.ui.TunerViewPanel;
import java.awt.Color;
import java.awt.Dimension;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
@@ -41,8 +43,6 @@ import org.slf4j.LoggerFactory;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
public class ControllerPanel extends JPanel
{
@@ -63,7 +63,7 @@ public class ControllerPanel extends JPanel
{
mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager,
playlistManager.getAliasModel());
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences);
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager);
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager);
mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences);

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* 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.controller.channel;
@@ -33,7 +32,7 @@ public class ChannelSelectionManager implements Listener<ChannelEvent>
public ChannelSelectionManager(ChannelModel channelModel)
{
mChannelEventListener = channelModel;
channelModel.addListener(this::receive);
channelModel.addListener(ChannelSelectionManager.this);
}
@Override

View File

@@ -1,31 +1,34 @@
/*******************************************************************************
* *********************************************************************************************************************
* sdr-trunk
* Copyright (C) 2014-2017 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
* This program is 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.
* 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/>
* *********************************************************************************************************************
******************************************************************************/
* 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.dsp.filter.channelizer;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class ChannelCalculator
{
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
/**
@@ -546,13 +549,13 @@ public class ChannelCalculator
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Channel Calculator Settings");
sb.append(" Channels:").append(getChannelCount());
sb.append(" Sample Rate:").append(getSampleRate());
sb.append(" Channel Bandwidth:").append(getChannelBandwidth());
sb.append(" Channel Rate:").append(getChannelSampleRate());
sb.append(" Min:").append(getMinimumFrequency());
sb.append(" Max:").append(getMaximumFrequency());
sb.append("Channel Calculator | Tuner SR:").append(FREQUENCY_FORMAT.format(getSampleRate() / 1E6D));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(getCenterFrequency() / 1E6D));
sb.append(" MIN:").append(FREQUENCY_FORMAT.format(getMinimumFrequency() / 1E6D));
sb.append(" MAX:").append(FREQUENCY_FORMAT.format(getMaximumFrequency() / 1E6D));
sb.append(" | Channel COUNT:").append(getChannelCount());
sb.append(" BW:").append(FREQUENCY_FORMAT.format(getChannelBandwidth() / 1E6D));
sb.append(" SR:").append(FREQUENCY_FORMAT.format(getChannelSampleRate() / 1E6D));
return sb.toString();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@ import io.github.dsheirer.source.tuner.TunerController;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.util.Dispatcher;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -43,6 +44,7 @@ import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,6 +67,7 @@ import org.slf4j.LoggerFactory;
*/
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
private static final double CHANNEL_OVERSAMPLING = 2.0;
@@ -123,6 +126,34 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate());
}
/**
* Provides a description of the state of this manager.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel Manager Providing [").append(mChannelSources.size()).append("] Channels");
sb.append("\n\t").append(mChannelCalculator);
for(PolyphaseChannelSource pcs: mChannelSources)
{
List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sampleRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
sb.append("\n\tPolyphase | Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
sb.append(" BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
sb.append(" | Channel CF: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
sb.append(" REQUESTED CF: ").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
sb.append(" MIXER:").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
sb.append(" | Polyphase Indices: ").append(indexes);
}
return sb.toString();
}
public void stopAllChannels()
{
mRunning = false;
@@ -278,7 +309,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
//no-op
break;
default:
mLog.info("Unrecognized source event: " + sourceEvent.toString());
mLog.info("Unrecognized source event: " + sourceEvent);
break;
}
}
@@ -427,9 +458,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
source.dispose();
}
break;
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
//ignore
break;
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
//Rebroadcast so that the tuner source can process this event
mSourceEventBroadcaster.broadcast(sourceEvent);
@@ -445,16 +473,18 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
/**
* Processes the incoming buffer stream from the provider and transfers the buffers to the polyphase channelizer.
*
* This monitor incorporates a queue for receiving source events that affect configuration of the channelizer so
* that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer
* and/or the channel sources and output processors.
* This monitor incorporates a source event handler that queues a center frequency update so that it can be
* handled on the buffer processing thread, avoiding having to lock on the output processor thread. Since we
* anticipate that these two threads will contend for access to this update required flag, we use an update lock
* to protect access to the flag.
*/
public class NativeBufferReceiver implements Listener<INativeBuffer>
{
private boolean mOutputProcessorUpdateRequired = false;
private ReentrantLock mUpdateFlagLock = new ReentrantLock();
/**
* Queues the source event for deferred execution on the buffer processing thread.
* Processes tuner center frequency change source events to flag when output processors need updating.
* @param event that affects configuration of the channelizer (frequency or sample rate change events)
*/
public void receive(SourceEvent event)
@@ -463,14 +493,31 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
if(mChannelCalculator.getCenterFrequency() != frequency)
{
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
mUpdateFlagLock.lock();
try
{
//Update the channel calculator frequency so that it's ready when the output processor update occurs
mChannelCalculator.setCenterFrequency(frequency);
mOutputProcessorUpdateRequired = true;
}
finally
{
mUpdateFlagLock.unlock();
}
}
}
/**
* Process native buffer streams and update polyphase output channels when the parent tuner center
* frequency changes.
* @param nativeBuffer of sample to process.
*/
@Override
public void receive(INativeBuffer nativeBuffer)
{
mUpdateFlagLock.lock();
try
{
if(mOutputProcessorUpdateRequired)
@@ -478,21 +525,28 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
updateOutputProcessors();
mOutputProcessorUpdateRequired = false;
}
}
finally
{
mUpdateFlagLock.unlock();
}
if(mPolyphaseChannelizer != null)
if(mPolyphaseChannelizer != null)
{
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
while(iterator.hasNext())
{
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
while(iterator.hasNext())
try
{
mPolyphaseChannelizer.receive(iterator.next());
}
catch(Throwable throwable)
{
mLog.error("Error", throwable);
}
}
}
catch(Throwable throwable)
{
mLog.error("Error", throwable);
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
@@ -45,9 +46,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
private double mChannelSampleRate;
private long mIndexCenterFrequency;
private long mChannelFrequencyCorrection;
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
private double mTunerSampleRate;
private double mTunerCenterFrequency;
/**
* Constructs an instance
*
@@ -66,6 +71,41 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
updateOutputProcessor(channelCalculator, filterManager);
}
/**
* Description of the state or configuration of the output processor.
*/
public String getStateDescription()
{
return mPolyphaseChannelOutputProcessor.getStateDescription();
}
/**
* Current output processor indexes.
* @return indexes
*/
public List<Integer> getOutputProcessorIndexes()
{
return mOutputProcessorIndexes;
}
/**
* Sample rate or bandwidth of the tuner providing input to the channelizer.
* @return sample rate
*/
public double getTunerSampleRate()
{
return mTunerSampleRate;
}
/**
* Center tuned frequency of the tuner providing input to the channelizer.
* @return center frequency
*/
public double getTunerCenterFrequency()
{
return mTunerCenterFrequency;
}
@Override
public void start()
{
@@ -125,6 +165,11 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
//calculator will throw an IllegalArgException ... handled below
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
mOutputProcessorIndexes.clear();
mOutputProcessorIndexes.addAll(indexes);
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
mTunerSampleRate = channelCalculator.getSampleRate();
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
@@ -137,7 +182,7 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
{
try
{
float[] filter = filterManager.getFilter(channelCalculator.getSampleRate(),
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
channelCalculator.getChannelBandwidth(), indexes.size());
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
}
@@ -263,59 +308,32 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
}
}
@Override
public long getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Adjusts the frequency correction value that is being applied to the channelized output stream by the
* polyphase channel output processor.
*
* @param value to apply for frequency correction in hertz
*/
protected void setChannelFrequencyCorrection(long value)
{
mChannelFrequencyCorrection = value;
updateFrequencyOffset();
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
}
/**
* Sets the center frequency for this channel.frequency for the incoming sample stream channel results and resets frequency correction to zero.
* Sets the center frequency for this channel.
* @param frequency in hertz
*/
@Override
public void setFrequency(long frequency)
{
mIndexCenterFrequency = frequency;
}
//Set frequency correction to zero to trigger an update to the mixer and allow downstream monitors to
//recalculate the frequency error correction again
setChannelFrequencyCorrection(0);
/**
* Center frequency from the incoming index channel(s).
* @return frequency in hertz
*/
public long getIndexCenterFrequency()
{
return mIndexCenterFrequency;
}
/**
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
* within the channel
*/
private long getFrequencyOffset()
public long getFrequencyOffset()
{
return mIndexCenterFrequency - getTunerChannel().getFrequency() + mChannelFrequencyCorrection;
}
/**
* Updates the frequency offset being applied by the output processor. Calculates the offset using the center
* frequency of the incoming polyphase channel results and the desired output channel frequency adjusted by any
* requested frequency correction value.
*/
private void updateFrequencyOffset()
{
if(mPolyphaseChannelOutputProcessor != null)
{
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
}
return mIndexCenterFrequency - getTunerChannel().getFrequency();
}
@Override

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
package io.github.dsheirer.dsp.filter.channelizer;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Arrays;
import org.apache.commons.math3.util.FastMath;
import org.jtransforms.fft.FloatFFT_1D;
import org.slf4j.Logger;
@@ -65,6 +66,20 @@ public class TwoChannelSynthesizerM2
init(filter);
}
/**
* Description of the state/configuration of this synthesizer to support debugging.
* @return state description.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two-Channel Synthesizer");
sb.append("\n\t\t Interleaved Filter: " + Arrays.toString(mIQInterleavedFilter));
sb.append("\n\t\t Serpentine Buffer Length: ").append(mSerpentineDataBuffer.length);
sb.append("\n\t\t Filter Vector Product Length: ").append(mFilterVectorProduct.length);
return sb.toString();
}
/**
* Initializes the synthesizer filter and data buffers for operation.
*
@@ -73,7 +88,6 @@ public class TwoChannelSynthesizerM2
private void init(float[] filter)
{
int tapsPerChannel = (int) FastMath.ceil(filter.length / 2);
mIQInterleavedFilter = getInterleavedFilter(filter, tapsPerChannel);
mSerpentineDataBuffer = new float[mIQInterleavedFilter.length];
mFilterVectorProduct = new float[mIQInterleavedFilter.length];

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,6 +24,11 @@ import java.util.List;
public interface IPolyphaseChannelOutputProcessor
{
/**
* Description of the state/configuration of this output processor
*/
String getStateDescription();
/**
* Start processing channel results
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -45,6 +45,16 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
mMixerAssembler.getMixer().setSampleRate(sampleRate);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("One Channel Output Processor");
sb.append("\n\tIndex (doubled) [").append(mChannelOffset).append("]");
sb.append("\n\tMixer Assembler: none");
return sb.toString();
}
@Override
public void setSynthesisFilter(float[] filter)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,6 +42,19 @@ public class TwoChannelMixerAssembler extends MixerAssembler
super(gain);
}
/**
* Description of the state/configuration of this mixer assembler.
*/
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two Channel Mixer Assembler - State Description:");
sb.append("\n\tTwo-Channel Synthesizer: ").append(mTwoChannelSynthesizer.getStateDescription());
sb.append("\n\tMixer Frequency: ").append(getMixer().getFrequency());
sb.append("\n\tGain: ").append(getGain().getGain());
return sb.toString();
}
/**
* Sets the filter coefficients to use for two-channel synthesis
* @param filter

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -49,6 +49,16 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
setSynthesisFilter(filter);
}
@Override
public String getStateDescription()
{
StringBuilder sb = new StringBuilder();
sb.append("Two Channel Output Processor");
sb.append("\n\tIndices (doubled) 1 [").append(mChannelOffset1).append("] 2 [").append(mChannelOffset2).append("]");
sb.append("\n\tMixer Assembler: ").append(mMixerAssembler.getStateDescription());
return sb.toString();
}
/**
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
*

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -41,7 +41,7 @@ public abstract class ComplexGain
* Gain value that will be applied to complex samples
* @return gain value
*/
protected float getGain()
public float getGain()
{
return mGain;
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -72,6 +72,15 @@ public abstract class ComplexMixer
return mOscillator.hasFrequency();
}
/**
* Current frequency of this oscillator
* @return frequency in hertz
*/
public double getFrequency()
{
return mOscillator.getFrequency();
}
/**
* Sets the sample rate of the underlying oscillator
* @param sampleRate in Hertz

View File

@@ -0,0 +1,117 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.oscillator;
import io.github.dsheirer.sample.complex.ComplexSamples;
import java.util.Random;
/**
* Additive white gaussian noise wrapper around a complex oscillator that mixes in gaussian noise to the complex
* samples generated by the underlying complex oscillator.
*/
public class AWGNOscillator implements IComplexOscillator
{
private final Random mRandom = new Random();
private IComplexOscillator mComplexOscillator;
private float mNoiseScale;
/**
* Constructs an instance
* @param complexOscillator to generate samples
* @param noiseScale to apply to generated samples to reduce the power of the noise and increase the SNR.
*/
public AWGNOscillator(IComplexOscillator complexOscillator, float noiseScale)
{
mComplexOscillator = complexOscillator;
mNoiseScale = noiseScale;
}
/**
* Produce the next scaled noise sample.
* @return next scaled noise sample.
*/
private float nextNoise()
{
return (float)mRandom.nextGaussian() * mNoiseScale;
}
@Override
public float[] generate(int sampleCount)
{
float[] generated = mComplexOscillator.generate(sampleCount);
for(int x = 0; x < generated.length; x++)
{
generated[x] += nextNoise();
}
return generated;
}
@Override
public ComplexSamples generateComplexSamples(int sampleCount, long timestamp)
{
ComplexSamples samples = mComplexOscillator.generateComplexSamples(sampleCount, timestamp);
for(int x = 0; x < samples.i().length; x++)
{
samples.i()[x] += nextNoise();
samples.q()[x] += nextNoise();
}
return samples;
}
@Override
public double getFrequency()
{
return mComplexOscillator.getFrequency();
}
@Override
public void setFrequency(double frequency)
{
mComplexOscillator.setFrequency(frequency);
}
@Override
public boolean hasFrequency()
{
return mComplexOscillator.hasFrequency();
}
@Override
public double getSampleRate()
{
return mComplexOscillator.getSampleRate();
}
@Override
public void setSampleRate(double sampleRate)
{
mComplexOscillator.setSampleRate(sampleRate);
}
@Override
public boolean isEnabled()
{
return mComplexOscillator.isEnabled();
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * 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/>
* * *****************************************************************************
* 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.dsp.psk.pll;
@@ -101,7 +98,9 @@ public class FrequencyCorrectionSyncMonitor implements ISyncDetectListener, IFre
@Override
public void processFrequencyError(long frequencyError)
{
//Only rebroadcast the frequency error if the sync count is more than 2
mFeedbackDecoder.broadcast(SourceEvent.pllFrequencyMeasurement(frequencyError));
//Only rebroadcast as a frequency error measurement if the sync count is more than 2
if(mSyncCount > 2)
{
mFeedbackDecoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.edac;
@@ -34,7 +31,7 @@ import org.slf4j.LoggerFactory;
*/
public class BerlekempMassey
{
public final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
private final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
/* Golay field size GF( 2 ** MM ) */
private int MM;

View File

@@ -0,0 +1,66 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.edac;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements the 5-bit Checksum error detection algorithm specified in TS 102 361-1 paragraph B.3.11
*/
public class Checksum_5_DMR
{
private static final Logger LOGGER = LoggerFactory.getLogger(Checksum_5_DMR.class);
private static final int[] LC_0 = {0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] LC_1 = {8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] LC_2 = {16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] LC_3 = {24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] LC_4 = {32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] LC_5 = {40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] LC_6 = {48, 49, 50, 51, 52, 53, 54, 55};
private static final int[] LC_7 = {56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] LC_8 = {64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] CHECKSUM = {72, 73, 74, 75, 76};
/**
* Indicates if the 77-bit Full Link Control message is valid.
* @param message to check
* @return true if the checksum matches.
*/
public static boolean isValid(CorrectedBinaryMessage message)
{
int accumulator = message.getInt(LC_0);
accumulator += message.getInt(LC_1);
accumulator += message.getInt(LC_2);
accumulator += message.getInt(LC_3);
accumulator += message.getInt(LC_4);
accumulator += message.getInt(LC_5);
accumulator += message.getInt(LC_6);
accumulator += message.getInt(LC_7);
accumulator += message.getInt(LC_8);
accumulator = accumulator % 31;
int checksum = message.getInt(CHECKSUM);
return accumulator == checksum;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,12 +20,16 @@
package io.github.dsheirer.edac;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Berlecamp-Massey decoder for Reed Solomon RS(12,9,4) protected messages using DMR generator polynomial
*/
public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
{
private final static Logger mLog = LoggerFactory.getLogger(ReedSolomon_12_9_4_DMR.class);
private static final int[] FLC_CODEWORD_0 = {0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] FLC_CODEWORD_1 = {8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] FLC_CODEWORD_2 = {16, 17, 18, 19, 20, 21, 22, 23};
@@ -56,16 +60,16 @@ public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
*
* @param message to be corrected.
* @param crcMask as defined in the TS 102-361-1 Table B.21
* @return true if the message was corrected.
* @return true if the message is correct or if the message was corrected.
*/
public boolean correctFullLinkControl(CorrectedBinaryMessage message, int crcMask)
{
int[] input = new int[255];
int[] output = new int[255];
input[0] = message.getInt(FLC_PARITY_2) ^ crcMask;
input[1] = message.getInt(FLC_PARITY_1) ^ crcMask;
input[2] = message.getInt(FLC_PARITY_0) ^ crcMask;
input[0] = (message.getInt(FLC_PARITY_2) ^ crcMask);
input[1] = (message.getInt(FLC_PARITY_1) ^ crcMask);
input[2] = (message.getInt(FLC_PARITY_0) ^ crcMask);
input[3] = message.getInt(FLC_CODEWORD_8);
input[4] = message.getInt(FLC_CODEWORD_7);
input[5] = message.getInt(FLC_CODEWORD_6);
@@ -106,4 +110,40 @@ public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
return true;
}
public static void main(String[] args)
{
int[] output = new int[255];
int[] input = new int[255];
input[0] = 247;
input[1] = 236;
input[2] = 171;
input[3] = 24;
input[4] = 46;
input[5] = 177;
input[6] = 246;
input[7] = 205;
input[8] = 232;
input[9] = 32;
input[10] = 124;
input[11] = 0;
// input[254] = 247;
// input[253] = 236;
// input[252] = 171;
// input[251] = 24;
// input[250] = 46;
// input[249] = 177;
// input[248] = 246;
// input[247] = 205;
// input[246] = 232;
// input[245] = 32;
// input[244] = 124;
// input[243] = 0;
ReedSolomon_12_9_4_DMR rs = new ReedSolomon_12_9_4_DMR();
boolean irrecoverable = rs.decode(input, output);
mLog.info("Irrecoverable: " + irrecoverable);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,6 +24,8 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMap;
import io.github.dsheirer.controller.channel.map.ChannelRange;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.dmr.DMRRecordingViewer;
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
import io.github.dsheirer.gui.icon.IconManager;
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
import io.github.dsheirer.gui.playlist.PlaylistEditor;
@@ -65,6 +67,7 @@ public class JavaFxWindowManager extends Application
public static final String USER_PREFERENCES_EDITOR = "preferences";
public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog";
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
public static final String STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER = "dmr.message.viewer";
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor";
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
@@ -79,12 +82,14 @@ public class JavaFxWindowManager extends Application
private TunerManager mTunerManager;
private UserPreferences mUserPreferences;
private UserPreferencesEditor mUserPreferencesEditor;
private DMRRecordingViewer mDmrRecordingViewer;
private Stage mChannelMapStage;
private Stage mIconManagerStage;
private Stage mJmbeEditorStage;
private Stage mPlaylistStage;
private Stage mUserPreferencesStage;
private Stage mDmrRecordingViewerStage;
/**
* Constructs an instance. Note: this constructor is used for Swing applications.
@@ -189,6 +194,34 @@ public class JavaFxWindowManager extends Application
return new CalibrationDialog(userPreferences);
}
/**
* Stage for the DMR Message Viewer
*/
public Stage getDmrRecordingViewerStage()
{
if(mDmrRecordingViewerStage == null)
{
createJFXPanel();
Scene scene = new Scene(getDmrRecordingViewer(), 1100, 800);
mDmrRecordingViewerStage = new Stage();
mDmrRecordingViewerStage.setTitle("sdrtrunk - DMR Recording Viewer");
mDmrRecordingViewerStage.setScene(scene);
mUserPreferences.getJavaFxPreferences().monitor(mDmrRecordingViewerStage, STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER);
}
return mDmrRecordingViewerStage;
}
public DMRRecordingViewer getDmrRecordingViewer()
{
if(mDmrRecordingViewer == null)
{
mDmrRecordingViewer = new DMRRecordingViewer();
}
return mDmrRecordingViewer;
}
public Stage getIconManagerStage()
{
if(mIconManagerStage == null)
@@ -416,6 +449,19 @@ public class JavaFxWindowManager extends Application
});
}
/**
* Process a channel map editor request
*/
@Subscribe
public void process(final ViewDmrRecordingViewerRequest request)
{
execute(() -> {
getDmrRecordingViewerStage().show();
getDmrRecordingViewerStage().requestFocus();
getDmrRecordingViewerStage().toFront();
});
}
@Override
public void start(Stage primaryStage) throws Exception
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ import io.github.dsheirer.controller.channel.ChannelAutoStartFrame;
import io.github.dsheirer.controller.channel.ChannelException;
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
import io.github.dsheirer.gui.preference.CalibrateRequest;
@@ -59,6 +60,26 @@ import io.github.dsheirer.spectrum.SpectralDisplayPanel;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import java.awt.AWTException;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import javafx.application.Platform;
import javafx.scene.control.ButtonType;
import jiconfont.icons.font_awesome.FontAwesome;
@@ -80,26 +101,6 @@ import javax.swing.UIManager;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.plaf.metal.MetalLookAndFeel;
import java.awt.AWTException;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
public class SDRTrunk implements Listener<TunerEvent>
{
@@ -247,7 +248,11 @@ public class SDRTrunk implements Listener<TunerEvent>
{
mMainGui.setVisible(true);
Tuner tuner = tunerSpectralDisplayManager.showFirstTuner();
updateTitle(tuner);
if(tuner != null)
{
updateTitle(tuner.getPreferredName());
}
}
if(calibrating && !GraphicsEnvironment.isHeadless())
@@ -413,9 +418,11 @@ public class SDRTrunk implements Listener<TunerEvent>
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
viewMenu.add(viewPlaylistItem);
JMenuItem preferencesItem = new JMenuItem("User Preferences");
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
viewMenu.add(preferencesItem);
viewMenu.add(new JSeparator());
JMenuItem dmrMessageViewerMenu = new JMenuItem("DMR Recording Viewer");
dmrMessageViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewDmrRecordingViewerRequest()));
viewMenu.add(dmrMessageViewerMenu);
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
@@ -436,15 +443,19 @@ public class SDRTrunk implements Listener<TunerEvent>
mLog.error("Couldn't open file explorer");
JOptionPane.showMessageDialog(mMainGui,
"Can't launch file explorer - files are located at: " +
getHomePath().toString(),
"Can't launch file explorer",
JOptionPane.ERROR_MESSAGE);
"Can't launch file explorer - files are located at: " +
getHomePath().toString(),
"Can't launch file explorer",
JOptionPane.ERROR_MESSAGE);
}
}
});
viewMenu.add(logFilesMenu);
JMenuItem preferencesItem = new JMenuItem("User Preferences");
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
viewMenu.add(preferencesItem);
viewMenu.add(new JSeparator());
viewMenu.add(new TunersMenu());
viewMenu.add(new JSeparator());
@@ -623,21 +634,34 @@ public class SDRTrunk implements Listener<TunerEvent>
@Override
public void receive(TunerEvent event)
{
if(event.getEvent() == TunerEvent.Event.REQUEST_MAIN_SPECTRAL_DISPLAY)
switch(event.getEvent())
{
updateTitle(event.getTuner());
case REQUEST_MAIN_SPECTRAL_DISPLAY:
updateTitle(event.getTuner().getPreferredName());
break;
case REQUEST_CLEAR_MAIN_SPECTRAL_DISPLAY:
updateTitle(null);
break;
case NOTIFICATION_SHUTTING_DOWN:
Tuner currentTuner = mSpectralPanel.getTuner();
if(event.hasTuner() && event.getTuner().equals(currentTuner) || currentTuner == null)
{
updateTitle(null);
}
break;
}
}
/**
* Updates the title bar with the tuner name
* @param tuner optional
* @param tunerName optional
*/
private void updateTitle(Tuner tuner)
private void updateTitle(String tunerName)
{
if(tuner != null)
if(tunerName != null)
{
mMainGui.setTitle(mTitle + " - " + tuner.getPreferredName());
mMainGui.setTitle(mTitle + " - " + tunerName);
}
else
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@ package io.github.dsheirer.gui.channelizer;
import io.github.dsheirer.buffer.FloatNativeBuffer;
import io.github.dsheirer.buffer.INativeBuffer;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.settings.SettingsManager;
@@ -36,6 +37,12 @@ import io.github.dsheirer.spectrum.ComplexDftProcessor;
import io.github.dsheirer.spectrum.DFTSize;
import io.github.dsheirer.spectrum.SpectrumPanel;
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,12 +57,6 @@ import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
public class ChannelizerViewer2 extends JFrame
{
@@ -194,9 +195,9 @@ public class ChannelizerViewer2 extends JFrame
getTopChannelArrayPanel().addChannel(tunerChannel);
}
catch(Exception exception)
catch(Exception e1)
{
mLog.error("Can't parse frequency from value: " + value);
mLog.error("Can't parse frequency from value: " + value, e1);
}
}
}
@@ -229,9 +230,9 @@ public class ChannelizerViewer2 extends JFrame
getBottomChannelArrayPanel().addChannel(tunerChannel);
}
catch(Exception exception)
catch(Exception e1)
{
mLog.error("Can't parse frequency from value: " + value);
mLog.error("Can't parse frequency from value: " + value, e1);
}
}
}
@@ -388,6 +389,26 @@ public class ChannelizerViewer2 extends JFrame
mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()));
});
if(mSource instanceof PolyphaseChannelSource pcs)
{ List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel - Bandwidth: ").append(sRate);
sb.append(" Channel/Requested/Offset: ").append(indexCenterFrequency);
sb.append("/").append(requestedCenterFrequency);
sb.append("/").append(appliedFrequencyOffset);
sb.append(" Indexes: ").append(indexes);
sb.append(" Tuner SR:").append(pcs.getTunerSampleRate());
sb.append(" Tuner CF:").append(pcs.getTunerCenterFrequency());
mLog.info(sb.toString());
}
mSource.start();
}
else

View File

@@ -0,0 +1,479 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.dmr;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.DMRMessageFramer;
import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.record.binary.BinaryReader;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.function.Predicate;
import java.util.prefs.Preferences;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility application to load and view a DMR .bits recording file with the messages fully parsed.
*/
public class DMRRecordingViewer extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(DMRRecordingViewer.class);
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory";
private Preferences mPreferences = Preferences.userNodeForPackage(DMRRecordingViewer.class);
private Scene mScene;
private Button mSelectFileButton;
private Label mSelectedFileLabel;
private TableView<IMessage> mMessageTableView;
private ObservableList<IMessage> mMessages = FXCollections.observableArrayList();
private FilteredList<IMessage> mFilteredMessages = new FilteredList<>(mMessages);
private CheckBox mShowTS0;
private CheckBox mShowTS1;
private CheckBox mShowTS2;
private CheckBox mUseCompressedTalkgroups;
private TextField mSearchText;
private TextField mFindText;
private Button mFindButton;
private Button mFindNextButton;
public DMRRecordingViewer()
{
setPadding(new Insets(5));
setSpacing(5);
HBox fileBox = new HBox();
fileBox.setMaxWidth(Double.MAX_VALUE);
fileBox.setAlignment(Pos.CENTER_LEFT);
fileBox.setSpacing(5);
HBox.setHgrow(getSelectFileButton(), Priority.NEVER);
HBox.setHgrow(getSelectedFileLabel(), Priority.ALWAYS);
getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER);
HBox compressedBox = new HBox();
compressedBox.setAlignment(Pos.CENTER_RIGHT);
compressedBox.setMaxWidth(Double.MAX_VALUE);
compressedBox.getChildren().addAll(getUseCompressedTalkgroups());
HBox.setHgrow(compressedBox, Priority.ALWAYS);
fileBox.getChildren().addAll(getSelectFileButton(), getSelectedFileLabel(), compressedBox);
HBox filterBox = new HBox();
filterBox.setMaxWidth(Double.MAX_VALUE);
filterBox.setAlignment(Pos.BASELINE_CENTER);
filterBox.setSpacing(5);
Label showLabel = new Label("Show:");
HBox.setMargin(showLabel, new Insets(0,0,0,15));
Label searchLabel = new Label("Message Filter:");
HBox.setMargin(searchLabel, new Insets(0,0,0,15));
Label findLabel = new Label("Find:");
HBox.setHgrow(getFindText(), Priority.ALWAYS);
HBox.setHgrow(getSearchText(), Priority.ALWAYS);
filterBox.getChildren().addAll(findLabel, getFindText(), getFindButton(), getFindNextButton(), searchLabel,
getSearchText(), showLabel, getShowTS0(), getShowTS1(), getShowTS2());
VBox.setVgrow(fileBox, Priority.NEVER);
VBox.setVgrow(filterBox, Priority.NEVER);
VBox.setVgrow(getMessageTableView(), Priority.ALWAYS);
getChildren().addAll(fileBox, filterBox, getMessageTableView());
}
/**
* Processes the recording file and loads the content into the viewer
* @param file containing a .bits recording of decoded DMR data.
*/
private void load(File file)
{
if(file != null && file.exists())
{
getSelectedFileLabel().setText(file.getName());
mMessages.clear();
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
DecodeConfigDMR config = new DecodeConfigDMR();
if(getUseCompressedTalkgroups().isSelected())
{
config.setUseCompressedTalkgroups(true);
}
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
messageFramer.setListener(messageProcessor);
messageProcessor.setMessageListener(message -> mMessages.add(message));
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
{
while(reader.hasNext())
{
ByteBuffer buffer = reader.next();
messageFramer.receive(buffer);
}
}
catch(Exception ioe)
{
ioe.printStackTrace();
}
}
}
/**
* Updates the filter(s) applies to the list of messages
*/
private void updateFilters()
{
Predicate<IMessage> timeslotPredicate = message ->
(getShowTS0().isSelected() && (message.getTimeslot() == 0)) ||
(getShowTS1().isSelected() && (message.getTimeslot() == 1)) ||
(getShowTS2().isSelected() && (message.getTimeslot() == 2));
String filterText = getSearchText().getText();
if(filterText == null || filterText.isEmpty())
{
mFilteredMessages.setPredicate(timeslotPredicate);
}
else
{
Predicate<IMessage> textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase());
mFilteredMessages.setPredicate(timeslotPredicate.and(textPredicate));
}
}
/**
* Finds and selects the first row containing the text argument.
* @param text to search for.
*/
private void find(String text)
{
if(text != null && !text.isEmpty())
{
for(IMessage message: mFilteredMessages)
{
if(message.toString().toLowerCase().contains(text.toLowerCase()))
{
getMessageTableView().getSelectionModel().select(message);
getMessageTableView().scrollTo(message);
return;
}
}
}
}
/**
* Finds and selects the first row containing the text argument, after the currently selected row.
* @param text to search for.
*/
private void findNext(String text)
{
if(text != null && !text.isEmpty())
{
IMessage selected = getMessageTableView().getSelectionModel().getSelectedItem();
if(selected == null)
{
find(text);
return;
}
int row = mFilteredMessages.indexOf(selected);
for(int x = row + 1; x < mFilteredMessages.size(); x++)
{
if(x < mFilteredMessages.size())
{
IMessage message = mFilteredMessages.get(x);
if(message.toString().toLowerCase().contains(text.toLowerCase()))
{
getMessageTableView().getSelectionModel().select(message);
getMessageTableView().scrollTo(message);
return;
}
}
}
}
}
/**
* List view control with DMR messages
*/
private TableView<IMessage> getMessageTableView()
{
if(mMessageTableView == null)
{
mMessageTableView = new TableView<>(mFilteredMessages);
TableColumn timestampColumn = new TableColumn();
timestampColumn.setPrefWidth(110);
timestampColumn.setText("Time");
timestampColumn.setCellValueFactory(new PropertyValueFactory<>("timestamp"));
TableColumn timeslotColumn = new TableColumn();
timeslotColumn.setPrefWidth(35);
timeslotColumn.setText("TS");
timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot"));
TableColumn validColumn = new TableColumn();
validColumn.setPrefWidth(50);
validColumn.setText("Valid");
validColumn.setCellValueFactory(new PropertyValueFactory<>("valid"));
TableColumn messageColumn = new TableColumn();
messageColumn.setPrefWidth(1000);
messageColumn.setText("Message");
messageColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures, ObservableValue>) param -> {
SimpleStringProperty property = new SimpleStringProperty();
if(param.getValue() instanceof IMessage message)
{
property.set(message.toString());
}
return property;
});
mMessageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn);
}
return mMessageTableView;
}
/**
* File selection button
* @return button
*/
private Button getSelectFileButton()
{
if(mSelectFileButton == null)
{
mSelectFileButton = new Button("Select ...");
mSelectFileButton.onActionProperty().set(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select DMR .bits Recording");
String lastDirectory = mPreferences.get(LAST_SELECTED_DIRECTORY, null);
if(lastDirectory != null)
{
File file = new File(lastDirectory);
if(file.exists() && file.isDirectory())
{
fileChooser.setInitialDirectory(file);
}
}
fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("sdrtrunk bits recording", "*.bits"));
File selected = fileChooser.showOpenDialog(getScene().getWindow());
if(selected != null)
{
mLog.info("Last Selected Directory updated to [" + selected.getParent() +"]");
mPreferences.put(LAST_SELECTED_DIRECTORY, selected.getParent());
load(selected);
}
});
}
return mSelectFileButton;
}
/**
* Selected file path label.
*/
private Label getSelectedFileLabel()
{
if(mSelectedFileLabel == null)
{
mSelectedFileLabel = new Label(" ");
}
return mSelectedFileLabel;
}
/**
* Check box to apply filter to show/hide TS0 messages
* @return check box control
*/
private CheckBox getShowTS0()
{
if(mShowTS0 == null)
{
mShowTS0 = new CheckBox("TS0");
mShowTS0.setSelected(true);
mShowTS0.setOnAction(event -> updateFilters());
}
return mShowTS0;
}
/**
* Check box to apply filter to show/hide TS0 messages
* @return check box control
*/
private CheckBox getShowTS1()
{
if(mShowTS1 == null)
{
mShowTS1 = new CheckBox("TS1");
mShowTS1.setSelected(true);
mShowTS1.setOnAction(event -> updateFilters());
}
return mShowTS1;
}
/**
* Check box to apply filter to show/hide TS0 messages
* @return check box control
*/
private CheckBox getShowTS2()
{
if(mShowTS2 == null)
{
mShowTS2 = new CheckBox("TS2");
mShowTS2.setSelected(true);
mShowTS2.setOnAction(event -> updateFilters());
}
return mShowTS2;
}
/**
* Search text filter box
* @return text control for entering search text
*/
private TextField getSearchText()
{
if(mSearchText == null)
{
mSearchText = new TextField();
mSearchText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
}
return mSearchText;
}
/**
* Text box for find text
*/
private TextField getFindText()
{
if(mFindText == null)
{
mFindText = new TextField();
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
}
return mFindText;
}
/**
* Find button to search for the text in the find text box.
* @return button
*/
private Button getFindButton()
{
if(mFindButton == null)
{
mFindButton = new Button("Find");
mFindButton.setOnAction(event -> find(getFindText().getText()));
}
return mFindButton;
}
/**
* Find next button to search for the text in the find text box.
* @return button
*/
private Button getFindNextButton()
{
if(mFindNextButton == null)
{
mFindNextButton = new Button("Next");
mFindNextButton.setOnAction(event -> findNext(getFindText().getText()));
}
return mFindNextButton;
}
/**
* Use Hytera Tier 3 Compressed Talkgroups
* @return
*/
private CheckBox getUseCompressedTalkgroups()
{
if(mUseCompressedTalkgroups == null)
{
mUseCompressedTalkgroups = new CheckBox("Use Compressed Talkgroups");
}
return mUseCompressedTalkgroups;
}
public static void main(String[] args)
{
Application viewer = new Application()
{
@Override
public void start(Stage primaryStage) throws Exception
{
Scene scene = new Scene(new DMRRecordingViewer(), 1100, 800);
primaryStage.setTitle("DMR Recording Viewer");
primaryStage.setScene(scene);
primaryStage.show();
}
};
Runnable r = () -> {
try
{
viewer.start(new Stage());
}
catch(Exception e)
{
mLog.error("Error starting DMR recording viewer application", e);
}
};
Platform.startup(r);
}
}

View File

@@ -0,0 +1,33 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.dmr;
import io.github.dsheirer.gui.JavaFxWindowRequest;
/**
* Request to show the DMR recording viewer
*/
public class ViewDmrRecordingViewerRequest extends JavaFxWindowRequest
{
/**
* Constructs an instance
*/
public ViewDmrRecordingViewerRequest() {}
}

View File

@@ -217,6 +217,9 @@ public class RadioIdEditor extends IdentifierEditor<Radio>
}
}
mLog.warn("Unable to find radio id editor for protocol [" + protocol + "] and format [" + integerFormat +
"] - using default editor");
//Use a default instance
for(RadioDetail detail: mRadioDetails)
{

View File

@@ -159,7 +159,7 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
else
{
mLog.warn("Couldn't find radio ID detail for protocol [" + getItem().getProtocol() +
"] and format [" + format + "]");
"] and format [" + format + "]");
getFormatLabel().setText(" ");
}
}
@@ -267,6 +267,9 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
}
}
mLog.warn("Unable to find radio id range editor for protocol [" + protocol + "] and format [" + integerFormat +
"] - using default editor");
//Use a default instance
for(RadioIdDetail detail: mRadioDetails)
{
@@ -287,20 +290,23 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
{
List<RadioIdDetail> details = new ArrayList<>();
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
new IntegerFormatter(0,0xFFFFFF), "Format: 0 - 16777215"));
new IntegerFormatter(0,0xFFFFFF), "Format: 0 - 16777215"));
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
new HexFormatter(0,0xFFFFFF), "Format: 0 - FFFFFF"));
new HexFormatter(0,0xFFFFFF), "Format: 0 - FFFFFF"));
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
new IntegerFormatter(0,0x7FFFFF), "Format: 0 - 8388607"));
new IntegerFormatter(0,0x7FFFFF), "Format: 0 - 8388607"));
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),
new HexFormatter(0,0x7FFFFF), "Format: 0 - 7FFFFF"));
new HexFormatter(0,0x7FFFFF), "Format: 0 - 7FFFFF"));
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new RadioIdDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - 16777215"));
details.add(new RadioIdDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
return details;
}

View File

@@ -135,7 +135,7 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
else
{
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
"] and format [" + format + "]");
"] and format [" + format + "]");
}
getFormatLabel().setText(talkgroupDetail.getTooltip());
@@ -219,6 +219,9 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
}
}
mLog.warn("Unable to find talkgroup editor for protocol [" + protocol + "] and format [" + integerFormat +
"] - using default editor");
//Use a default instance
for(TalkgroupDetail detail: mTalkgroupDetails)
{
@@ -236,37 +239,37 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
{
mTalkgroupDetails.clear();
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
"Format: 0 - 65535"));
"Format: 0 - 65535"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
"Format: 0 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
"Format: 0 - 16,777,215"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
"Format: 0 - FFFFFF"));
"Format: 0 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFFFF),
"Format: 1 - 16,777,215"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFFFF),
"Format: 1 - FFFFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
"Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
"Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
"Format: 0 - 65535"));
"Format: 0 - 65535"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
"Format: 0 - FFFF"));
"Format: 0 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFF),
"Format: 1 - 65535"));
"Format: 1 - 65535"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFF),
"Format: 1 - FFFF"));
"Format: 1 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
"Format: 0 - 65535"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
"Format: 0 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
"Format: 0 - FFFFFF"));
"Format: 0 - FFFFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
"Format: 0 - FFFFFF"));
"Format: 0 - FFFFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
"Format: 0 - FFFFFF"));
"Format: 0 - FFFFFF"));
}
public static class TalkgroupDetail

View File

@@ -161,7 +161,7 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
else
{
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
"] and format [" + format + "]");
"] and format [" + format + "]");
getFormatLabel().setText(" ");
}
}
@@ -269,6 +269,9 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
}
}
mLog.warn("Unable to find talkgroup range editor for protocol [" + protocol + "] and format [" + integerFormat +
"] - using default editor");
//Use a default instance
for(TalkgroupDetail detail: mTalkgroupDetails)
{
@@ -289,31 +292,35 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
{
List<TalkgroupDetail> details = new ArrayList<>();
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
new IntegerFormatter(0,65535), "Format: 0 - 65535"));
new IntegerFormatter(0,65535), "Format: 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
new HexFormatter(0,65535), "Format: 0 - FFFF"));
new HexFormatter(0,65535), "Format: 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
details.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
new LtrFormatter(0,0x3FFF), "Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
new LtrFormatter(0,0x3FFF), "Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
"Format: PPP-IIII = Prefix (0-127), Ident (1-8191)"));
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
"Format: PPP-IIII = Prefix (0-127), Ident (1-8191)"));
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
new IntegerFormatter(0,16777215), "Format: 0 - 16777215"));
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(1,16777215),
new IntegerFormatter(1,16777215), "Format: 1 - 16777215"));
details.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0XFFFFFF),
new HexFormatter(1, 0xFFFFFF), "Format: 0 - FFFFFF"));
return details;
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,6 +38,8 @@ import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
@@ -65,9 +67,6 @@ import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* DMR channel configuration editor
*/
@@ -83,6 +82,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
private RecordConfigurationEditor mRecordConfigurationEditor;
private ToggleSwitch mIgnoreDataCallsButton;
private ToggleSwitch mIgnoreCRCChecksumsButton;
private ToggleSwitch mUseCompressedTalkgroupsToggle;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
private IntegerTextField mLogicalSlotNumberField;
@@ -164,6 +164,14 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
GridPane.setConstraints(ignoreCRCLabel, 5, row);
gridPane.getChildren().add(ignoreCRCLabel);
GridPane.setConstraints(getUseCompressedTalkgroupsToggle(), 6, row);
gridPane.getChildren().add(getUseCompressedTalkgroupsToggle());
Label useCompressedTalkgroupsLabel = new Label("Use Compressed Talkgroups");
GridPane.setHalignment(useCompressedTalkgroupsLabel, HPos.LEFT);
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
@@ -512,6 +520,24 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
return mIgnoreCRCChecksumsButton;
}
/**
* Use compressed talkgroups toggle switch. Let's the user select to turn on compressed talkgroups for Hytera
* Tier-III systems.
* @return toggle.
*/
private ToggleSwitch getUseCompressedTalkgroupsToggle()
{
if(mUseCompressedTalkgroupsToggle == null)
{
mUseCompressedTalkgroupsToggle = new ToggleSwitch();
mUseCompressedTalkgroupsToggle.setTooltip(new Tooltip("Use Compressed Talkgroups. This is only for Hytera Tier-III Trunked Systems"));
mUseCompressedTalkgroupsToggle.setDisable(true);
mUseCompressedTalkgroupsToggle.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mUseCompressedTalkgroupsToggle;
}
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
{
if(mTrafficChannelPoolSizeSpinner == null)
@@ -578,6 +604,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
{
getIgnoreCRCChecksumsButton().setDisable(config == null);
getIgnoreDataCallsButton().setDisable(config == null);
getUseCompressedTalkgroupsToggle().setDisable(config == null);
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
getTimeslotTable().getItems().clear();
getTimeslotTable().setDisable(config == null);
@@ -597,6 +624,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
getIgnoreCRCChecksumsButton().setSelected(decodeConfig.getIgnoreCRCChecksums());
getUseCompressedTalkgroupsToggle().setSelected(decodeConfig.isUseCompressedTalkgroups());
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
@@ -608,6 +636,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
{
getIgnoreCRCChecksumsButton().setSelected(false);
getIgnoreDataCallsButton().setSelected(false);
getUseCompressedTalkgroupsToggle().setSelected(false);
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
}
@@ -630,6 +659,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
config.setIgnoreCRCChecksums(getIgnoreCRCChecksumsButton().isSelected());
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
config.setUseCompressedTalkgroups(getUseCompressedTalkgroupsToggle().isSelected());
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));
getItem().setDecodeConfiguration(config);
}

View File

@@ -1,12 +1,52 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.power;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
import io.github.dsheirer.dsp.squelch.ISquelchConfiguration;
import io.github.dsheirer.gui.control.DbPowerMeter;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.ComplexSamplesToNativeBufferModule;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.Source;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
import io.github.dsheirer.spectrum.ComplexDftProcessor;
import io.github.dsheirer.spectrum.FrequencyOverlayPanel;
import io.github.dsheirer.spectrum.SpectrumPanel;
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
@@ -16,9 +56,12 @@ import org.slf4j.LoggerFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import java.awt.EventQueue;
import java.text.DecimalFormat;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.MouseInputAdapter;
/**
* Display for channel power and squelch details
@@ -27,28 +70,42 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
{
private static final Logger mLog = LoggerFactory.getLogger(ChannelPowerPanel.class);
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private static final String NOT_AVAILABLE = "Not Available";
private PlaylistManager mPlaylistManager;
private ProcessingChain mProcessingChain;
private ComplexSamplesToNativeBufferModule mSampleStreamTapModule = new ComplexSamplesToNativeBufferModule();
private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor();
private DFTResultsConverter mDFTResultsConverter = new ComplexDecibelConverter();
private JLayeredPane mLayeredPanel;
private SpectrumPanel mSpectrumPanel;
private FrequencyOverlayPanel mFrequencyOverlayPanel;
private DbPowerMeter mPowerMeter = new DbPowerMeter();
private PeakMonitor mPeakMonitor = new PeakMonitor(DbPowerMeter.DEFAULT_MINIMUM_POWER);
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
private SpinnerNumberModel mNoiseFloorSpinnerModel;
private JSpinner mNoiseFloorSpinner;
private JLabel mPowerLabel;
private JLabel mPeakLabel;
private JLabel mSquelchLabel;
private JLabel mSquelchValueLabel;
private JLabel mPllFrequencyLabel;
private JLabel mPllFrequencyValueLabel;
private JButton mSquelchUpButton;
private JButton mSquelchDownButton;
private JButton mLogIndexesButton;
private double mSquelchThreshold;
private boolean mPanelVisible = false;
private boolean mDftProcessing = false;
/**
* Constructs an instance.
*/
public ChannelPowerPanel(PlaylistManager playlistManager)
public ChannelPowerPanel(PlaylistManager playlistManager, SettingsManager settingsManager)
{
mPlaylistManager = playlistManager;
setLayout(new MigLayout("", "[][grow,fill]", "[grow,fill]"));
setLayout(new MigLayout("", "[][][][grow,fill]", "[grow,fill]"));
mPowerMeter.setPeakVisible(true);
mPowerMeter.setSquelchThresholdVisible(true);
add(mPowerMeter);
@@ -85,6 +142,184 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
valuePanel.add(mSquelchDownButton);
add(valuePanel);
add(new JSeparator(JSeparator.VERTICAL));
JPanel fftPanel = new JPanel();
fftPanel.setLayout(new MigLayout("insets 0", "[grow,fill]", "[][grow,fill]"));
JPanel labelPanel = new JPanel();
labelPanel.setLayout(new MigLayout("insets 0", "[grow,fill][right][grow,fill][right][grow,fill][]", ""));
labelPanel.add(new JLabel("Channel Spectrum"));
mPllFrequencyLabel = new JLabel("PLL:");
mPllFrequencyLabel.setEnabled(false);
labelPanel.add(mPllFrequencyLabel);
mPllFrequencyValueLabel = new JLabel("0 Hz");
mPllFrequencyValueLabel.setEnabled(false);
labelPanel.add(mPllFrequencyValueLabel);
mNoiseFloorSpinnerModel = new SpinnerNumberModel(18, 8, 36, 1);
mNoiseFloorSpinnerModel.addChangeListener(e -> {
Number number = mNoiseFloorSpinnerModel.getNumber();
mSpectrumPanel.setSampleSize(number.doubleValue());
});
mNoiseFloorSpinner = new JSpinner(mNoiseFloorSpinnerModel);
labelPanel.add(mNoiseFloorSpinner);
labelPanel.add(new JLabel("Noise Floor"));
mLogIndexesButton = new JButton("Log Settings");
mLogIndexesButton.addActionListener(e -> {
if(mProcessingChain != null)
{
Source source = mProcessingChain.getSource();
if(source instanceof PolyphaseChannelSource pcs)
{
List<Integer> indexes = pcs.getOutputProcessorIndexes();
double sampleRate = pcs.getSampleRate();
long indexCenterFrequency = pcs.getIndexCenterFrequency();
long appliedFrequencyOffset = pcs.getFrequencyOffset();
long requestedCenterFrequency = pcs.getFrequency();
StringBuilder sb = new StringBuilder();
sb.append("Polyphase Channel - BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
sb.append(" Center/Requested/Mixer: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
sb.append("/").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
sb.append("/").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
sb.append(" Polyphase Indexes: ").append(indexes);
sb.append(" Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
mLog.info(sb.toString());
mLog.info("Output Processor: " + pcs.getStateDescription());
}
else if(source instanceof HalfBandTunerChannelSource<?> hbtcs)
{
StringBuilder sb = new StringBuilder();
sb.append("Heterodyne Channel - CF:").append(FREQUENCY_FORMAT.format(hbtcs.getFrequency() / 1E6d));
sb.append(" SR:").append(FREQUENCY_FORMAT.format(hbtcs.getSampleRate() / 1E6d));
sb.append(" Mixer:").append(FREQUENCY_FORMAT.format(hbtcs.getMixerFrequency() / 1E6d));
mLog.info(sb.toString());
}
else
{
mLog.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
}
}
});
labelPanel.add(mLogIndexesButton);
fftPanel.add(labelPanel, "wrap");
mFrequencyOverlayPanel = new FrequencyOverlayPanel(settingsManager);
mSpectrumPanel = new SpectrumPanel(settingsManager);
mSpectrumPanel.setSampleSize(18.0);
/**
* The layered pane holds the overlapping spectrum and channel panels
* and manages the sizing of each panel with the resize listener
*/
mLayeredPanel = new JLayeredPane();
mLayeredPanel.addComponentListener(new ResizeListener());
/**
* Create a mouse adapter to handle mouse events over the spectrum
* and waterfall panels
*/
MouseEventProcessor mouser = new MouseEventProcessor();
mFrequencyOverlayPanel.addMouseListener(mouser);
mFrequencyOverlayPanel.addMouseMotionListener(mouser);
mFrequencyOverlayPanel.addMouseWheelListener(mouser);
//Add the spectrum and channel panels to the layered panel
mLayeredPanel.add(mSpectrumPanel, 0, 0);
mLayeredPanel.add(mFrequencyOverlayPanel, 1, 0);
fftPanel.add(mLayeredPanel);
add(fftPanel);
mSampleStreamTapModule.setListener(mComplexDftProcessor);
mComplexDftProcessor.addConverter(mDFTResultsConverter);
mDFTResultsConverter.addListener(mSpectrumPanel);
mSpectrumPanel.clearSpectrum();
}
/**
* Signals this panel to indicate if this panel is visible to turn on the FFT processor when the panel is visible
* and turn off the FFT processor when it's not.
*
* Note: this method is intended to be called by the Swing event thread to ensure that only a single thread is
* invoking either this method, or the receive() method, since there is no thread synchronization between these
* two methods and they each depend on stable access to the mPanelVisible variable.
*
* @param visible true to indicate that this panel is showing/visible.
*/
public void setPanelVisible(boolean visible)
{
mPanelVisible = visible;
updateFFTProcessing();
}
/**
* Updates processing state for the DFT processor. Turns on DFT processing when we have a processing chain and
* when the user has this tab selected and visible. Otherwise, turns off DFT processing.
*/
private void updateFFTProcessing()
{
if(mPanelVisible && mProcessingChain != null)
{
startDftProcessing();
}
else
{
stopDftProcessing();
}
}
/**
* Starts DFT processing
*/
private void startDftProcessing()
{
if(!mDftProcessing)
{
mDftProcessing = true;
mSampleStreamTapModule.setListener(mComplexDftProcessor);
mComplexDftProcessor.start();
}
}
/**
* Stops DFT processing
*/
private void stopDftProcessing()
{
if(mDftProcessing)
{
mSampleStreamTapModule.removeListener();
mComplexDftProcessor.stop();
mSpectrumPanel.clearSpectrum();
mDftProcessing = false;
}
}
/**
* Updates the decoder's PLL frequency on the swing dispatch thread
* @param trackingFrequency that is currently measured.
*/
private void updatePllFrequency(long trackingFrequency)
{
//Note: we flip the sign on the error measurement because the value represents the amount of offset the PLL
//has to apply to move the signal to center/baseband
EventQueue.invokeLater(() -> {
String formattedValue = NumberFormat.getInstance().format(-trackingFrequency);
mPllFrequencyValueLabel.setText(formattedValue + " Hz");
mPllFrequencyValueLabel.setEnabled(true);
mPllFrequencyLabel.setEnabled(true);
});
mFrequencyOverlayPanel.setPllTrackingFrequency(trackingFrequency);
}
/**
@@ -124,6 +359,13 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
mPeakLabel.setText("0");
mPowerLabel.setText("0");
mPllFrequencyLabel.setEnabled(false);
mPllFrequencyValueLabel.setText("0 Hz");
mPllFrequencyValueLabel.setEnabled(false);
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, 0));
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(0));
mFrequencyOverlayPanel.setPllTrackingFrequency(0);
mFrequencyOverlayPanel.setChannelBandwidth(0);
mSquelchLabel.setEnabled(false);
mSquelchValueLabel.setText("Not Available");
@@ -138,9 +380,11 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
@Override
public void receive(ProcessingChain processingChain)
{
//Disconnect the FFT panel
if(mProcessingChain != null)
{
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
mProcessingChain.removeModule(mSampleStreamTapModule);
}
//Invoking reset - we're on the Swing dispatch thread here
@@ -151,11 +395,35 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
if(mProcessingChain != null)
{
mProcessingChain.addSourceEventListener(mSourceEventProcessor);
mProcessingChain.addModule(mSampleStreamTapModule);
Source source = mProcessingChain.getSource();
if(source instanceof TunerChannelSource tcs)
{
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, tcs.getFrequency()));
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(tcs.getSampleRate()));
}
Channel channel = mPlaylistManager.getChannelProcessingManager().getChannel(mProcessingChain);
if(channel != null)
{
TunerChannel tunerChannel = channel.getTunerChannel();
if(tunerChannel != null)
{
mFrequencyOverlayPanel.setChannelBandwidth(tunerChannel.getBandwidth());
}
}
}
updateFFTProcessing();
broadcast(SourceEvent.requestCurrentSquelchThreshold(null));
}
/**
* Processor for source event stream to capture power level and squelch related source events.
*/
@@ -171,17 +439,12 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
final double power = sourceEvent.getValue().doubleValue();
final double peak = mPeakMonitor.process(power);
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mPowerMeter.setPower(power);
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
EventQueue.invokeLater(() -> {
mPowerMeter.setPower(power);
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
mPowerMeter.setPeak(peak);
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
}
mPowerMeter.setPeak(peak);
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
});
}
@@ -205,7 +468,70 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
}
});
}
case NOTIFICATION_PLL_FREQUENCY ->
{
if(sourceEvent.hasValue())
{
updatePllFrequency(sourceEvent.getValue().longValue());
}
}
}
}
}
/**
* Monitors the sizing of the layered pane and resizes the spectrum and
* channel panels whenever the layered pane is resized
*/
public class ResizeListener implements ComponentListener
{
@Override public void componentResized(ComponentEvent e)
{
Component c = e.getComponent();
mSpectrumPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
mFrequencyOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
}
@Override public void componentHidden(ComponentEvent arg0) {}
@Override public void componentMoved(ComponentEvent arg0) {}
@Override public void componentShown(ComponentEvent arg0) {}
}
/**
* Mouse event handler for the spectral display panel.
*/
public class MouseEventProcessor extends MouseInputAdapter
{
public MouseEventProcessor()
{
}
@Override public void mouseWheelMoved(MouseWheelEvent e) {}
@Override public void mouseMoved(MouseEvent event)
{
update(event);
}
@Override public void mouseDragged(MouseEvent event) {}
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseClicked(MouseEvent event) {}
/**
* Updates the cursor display while the mouse is performing actions
*/
private void update(MouseEvent event)
{
mFrequencyOverlayPanel.setCursorLocation(event.getPoint());
}
@Override public void mouseEntered(MouseEvent e)
{
mFrequencyOverlayPanel.setCursorVisible(true);
}
@Override public void mouseExited(MouseEvent e)
{
mFrequencyOverlayPanel.setCursorVisible(false);
}
}
}

View File

@@ -53,7 +53,7 @@ public class PreferenceEditorFactory
return new DirectoryPreferenceEditor(userPreferences);
case JMBE_LIBRARY:
return new JmbeLibraryPreferenceEditor(userPreferences);
case SOURCE_TUNER_CHANNELIZER:
case SOURCE_TUNERS:
return new TunerPreferenceEditor(userPreferences);
case TALKGROUP_FORMAT:
return new TalkgroupFormatPreferenceEditor(userPreferences);

View File

@@ -31,7 +31,7 @@ public enum PreferenceEditorType
AUDIO_RECORD("Record"),
AUDIO_OUTPUT("Output/Tones"),
AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"),
SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"),
SOURCE_TUNERS("Tuners"),
TALKGROUP_FORMAT("Talkgroup & Radio ID"),
VECTOR_CALIBRATION("Vector Calibration"),
DEFAULT("Default");

View File

@@ -22,6 +22,8 @@ package io.github.dsheirer.gui.preference;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
import io.github.dsheirer.preference.UserPreferences;
import java.util.EnumMap;
import java.util.Map;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
@@ -44,9 +46,6 @@ import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.EnumMap;
import java.util.Map;
/**
* Preferences editor dialog
*/
@@ -196,7 +195,7 @@ public class UserPreferencesEditor extends BorderPane
storageItem.setExpanded(true);
TreeItem<String> sourceItem = new TreeItem<>("Source");
sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_TUNER_CHANNELIZER));
sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_TUNERS));
treeRoot.getChildren().add(sourceItem);
sourceItem.setExpanded(true);

View File

@@ -0,0 +1,62 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.preference.tuner;
/**
* RSPduo tuner select mode preferences.
*/
public enum RspDuoSelectionMode
{
DUAL("Dual Tuner"),
SINGLE_1("Single Tuner 1"),
SINGLE_2("Single Tuner 2");
private String mLabel;
RspDuoSelectionMode(String label)
{
mLabel = label;
}
@Override
public String toString()
{
return mLabel;
}
/**
* Lookup the enum entry from the value.
* @param value to match
* @return matched value or (default) DUAL if the value couldn't be matched.
*/
public static RspDuoSelectionMode fromValue(String value)
{
try
{
return RspDuoSelectionMode.valueOf(value);
}
catch(Exception e)
{
//Do nothing
}
return DUAL;
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
*
* * ******************************************************************************
* * 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/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.preference.tuner;
@@ -55,6 +52,8 @@ public class TunerPreferenceEditor extends HBox
private Label mHelpTextPolyphaseLabel;
private Label mHeterodyneLabel;
private Label mHelpTextHeterodyneLabel;
private ChoiceBox<RspDuoSelectionMode> mRspDuoTunerModeChoiceBox;
private Label mRspDuoModeLabel;
public TunerPreferenceEditor(UserPreferences userPreferences)
{
@@ -66,19 +65,24 @@ public class TunerPreferenceEditor extends HBox
{
if(mEditorPane == null)
{
int row = 0;
mEditorPane = new GridPane();
mEditorPane.setVgap(10);
mEditorPane.setHgap(10);
mEditorPane.setPadding(new Insets(10, 10, 10, 10));
GridPane.setHalignment(getChannelizerLabel(), HPos.LEFT);
mEditorPane.add(getChannelizerLabel(), 0, 0);
mEditorPane.add(getChannelizerTypeChoiceBox(), 1, 0);
mEditorPane.add(new Separator(Orientation.HORIZONTAL), 0, 1, 2, 1);
mEditorPane.add(getPolyphaseLabel(), 0, 2, 2, 1);
mEditorPane.add(getHelpTextPolyphaseLabel(), 0, 3, 2, 3);
mEditorPane.add(new Label(" "), 0, 6);
mEditorPane.add(getHeterodyneLabel(), 0, 7, 2, 1);
mEditorPane.add(getHelpTextHeterodyneLabel(), 0, 8, 2, 3);
GridPane.setHalignment(getChannelizerLabel(), HPos.RIGHT);
mEditorPane.add(getChannelizerLabel(), 0, row);
mEditorPane.add(getChannelizerTypeChoiceBox(), 1, row);
mEditorPane.add(getPolyphaseLabel(), 0, ++row, 2, 1);
mEditorPane.add(getHelpTextPolyphaseLabel(), 0, ++row, 2, 3);
row += 3;
mEditorPane.add(new Label(" "), 0, row);
mEditorPane.add(getHeterodyneLabel(), 0, ++row, 2, 1);
mEditorPane.add(getHelpTextHeterodyneLabel(), 0, ++row, 2, 3);
row += 3;
mEditorPane.add(new Separator(Orientation.HORIZONTAL), 0, row, 2, 1);
mEditorPane.add(getRspDuoModeLabel(), 0, ++row);
mEditorPane.add(getRspDuoTunerModeChoiceBox(), 1, row);
}
return mEditorPane;
@@ -161,4 +165,40 @@ public class TunerPreferenceEditor extends HBox
return mHelpTextHeterodyneLabel;
}
private ChoiceBox<RspDuoSelectionMode> getRspDuoTunerModeChoiceBox()
{
if(mRspDuoTunerModeChoiceBox == null)
{
mRspDuoTunerModeChoiceBox = new ChoiceBox<>();
mRspDuoTunerModeChoiceBox.getItems().addAll(RspDuoSelectionMode.values());
RspDuoSelectionMode current = mTunerPreference.getRspDuoTunerMode();
mRspDuoTunerModeChoiceBox.getSelectionModel().select(current);
mRspDuoTunerModeChoiceBox.setOnAction(event -> {
RspDuoSelectionMode selected = mRspDuoTunerModeChoiceBox.getSelectionModel().getSelectedItem();
mTunerPreference.setRspDuoTunerMode(selected);
Label label = new Label("Please restart the application for this change to take effect");
label.setWrapText(true);
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.getDialogPane().setContent(label);
alert.initOwner(((Node)getRspDuoTunerModeChoiceBox()).getScene().getWindow());
alert.show();
});
}
return mRspDuoTunerModeChoiceBox;
}
private Label getRspDuoModeLabel()
{
if(mRspDuoModeLabel == null)
{
mRspDuoModeLabel = new Label("SDRPlay RSPduo Selection Mode");
}
return mRspDuoModeLabel;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -53,6 +53,7 @@ public enum Form
SITE,
STATE,
SYSTEM,
TALKER_ALIAS,
TALKGROUP,
TELEPHONE_NUMBER,
TONE,

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,7 +19,6 @@
package io.github.dsheirer.identifier;
import io.github.dsheirer.protocol.Protocol;
import java.util.Objects;
/**
@@ -70,6 +69,15 @@ public abstract class Identifier<T>
return mValue;
}
/**
* Allow subclass implementations to set or update the value after construction.
* @param value to update or set.
*/
protected void setValue(T value)
{
mValue = value;
}
/**
* Class/type of identifier
*/

View File

@@ -0,0 +1,54 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.identifier.alias;
import io.github.dsheirer.protocol.Protocol;
/**
* Talker alias value provided by the network for the current talker (ie FROM).
*/
public class DmrTalkerAliasIdentifier extends TalkerAliasIdentifier
{
/**
* Constructs an instance.
* @param value of the talker alias
*/
public DmrTalkerAliasIdentifier(String value)
{
super(value);
}
@Override
public boolean isValid()
{
return getValue() != null && !getValue().isEmpty();
}
@Override
public Protocol getProtocol()
{
return Protocol.DMR;
}
public static DmrTalkerAliasIdentifier create(String value)
{
return new DmrTalkerAliasIdentifier(value);
}
}

View File

@@ -0,0 +1,46 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.identifier.alias;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.string.StringIdentifier;
/**
* Talker alias value provided by the network for the current talker (ie FROM).
*/
public abstract class TalkerAliasIdentifier extends StringIdentifier
{
/**
* Constructs an instance.
* @param value of the string/alias identifier
*/
public TalkerAliasIdentifier(String value)
{
super(value, IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
}
@Override
public boolean isValid()
{
return getValue() != null && !getValue().isEmpty();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -72,14 +72,15 @@ import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.heartbeat.Heartbeat;
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processing chain provides a framework for connecting a complex or real sample
@@ -116,12 +117,15 @@ public class ProcessingChain implements Listener<ChannelEvent>
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private List<Module> mModules = new ArrayList<>();
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
private MessageHistory mMessageHistory = new MessageHistory(500);
private AbstractChannelState mChannelState;
private EventBus mEventBus;
protected Source mSource;
//Lock to protect access to the modules list.
private ReentrantLock mModuleLock = new ReentrantLock();
private List<Module> mModules = new ArrayList<>();
/**
* Creates a processing chain for managing a set of modules
@@ -196,30 +200,46 @@ public class ProcessingChain implements Listener<ChannelEvent>
*/
public void removeTrafficChannelManager()
{
Iterator<Module> it = mModules.iterator();
mModuleLock.lock();
while(it.hasNext())
try
{
if(it.next() instanceof TrafficChannelManager)
Iterator<Module> it = mModules.iterator();
while(it.hasNext())
{
it.remove();
if(it.next() instanceof TrafficChannelManager)
{
it.remove();
}
}
}
finally
{
mModuleLock.unlock();
}
}
public void dispose()
{
stop();
List<Module> modules = new ArrayList<>(mModules);
mModuleLock.lock();
for(Module module : modules)
try
{
removeModule(module);
module.dispose();
}
List<Module> modulesToRemove = new ArrayList<>(mModules);
mModules.clear();
for(Module module : modulesToRemove)
{
removeModule(module);
module.dispose();
}
}
finally
{
mModuleLock.unlock();
}
mAudioSegmentBroadcaster.dispose();
mDecodeEventBroadcaster.dispose();
@@ -269,12 +289,21 @@ public class ProcessingChain implements Listener<ChannelEvent>
addModule(mSource);
}
/**
* Source of sample data for this channel.
* @return source, may be null.
*/
public Source getSource()
{
return mSource;
}
/**
* List of current modules for this processing chain
*/
public List<Module> getModules()
{
return mModules;
return Collections.unmodifiableList(mModules);
}
/**
@@ -284,13 +313,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<DecoderState> decoderStates = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof DecoderState)
for(Module module : mModules)
{
decoderStates.add((DecoderState)module);
if(module instanceof DecoderState)
{
decoderStates.add((DecoderState)module);
}
}
}
finally
{
mModuleLock.unlock();
}
return decoderStates;
}
@@ -322,7 +360,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
*/
public void addModule(Module module)
{
mModules.add(module);
mModuleLock.lock();
try
{
mModules.add(module);
}
finally
{
mModuleLock.unlock();
}
module.setInterModuleEventBus(getEventBus());
registerListeners(module);
registerProviders(module);
@@ -337,7 +385,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
unregisterListeners(module);
unregisterProviders(module);
module.setInterModuleEventBus(null);
mModules.remove(module);
mModuleLock.lock();
try
{
mModules.remove(module);
}
finally
{
mModuleLock.unlock();
}
}
/**
@@ -656,18 +714,27 @@ public class ProcessingChain implements Listener<ChannelEvent>
+ "sample type - cannot start processing chain");
}
/* Start each of the modules */
for(Module module : mModules)
mModuleLock.lock();
try
{
try
/* Start each of the modules */
for(Module module : mModules)
{
module.start();
}
catch(Exception e)
{
mLog.error("Error starting module", e);
try
{
module.start();
}
catch(Exception e)
{
mLog.error("Error starting module", e);
}
}
}
finally
{
mModuleLock.unlock();
}
}
else
{
@@ -708,15 +775,28 @@ public class ProcessingChain implements Listener<ChannelEvent>
}
/* Stop each of the remaining modules */
for(Module module : mModules)
{
module.stop();
}
mModuleLock.lock();
//Reset each of the modules
for(Module module : mModules)
try
{
module.reset();
for(Module module : mModules)
{
module.stop();
}
//Reset each of the modules
for(Module module : mModules)
{
module.reset();
}
}
catch(Exception e)
{
mLog.error("Error stopping or resetting modules", e);
}
finally
{
mModuleLock.unlock();
}
}
}
@@ -728,13 +808,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<Module> eventLoggingModules = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof EventLogger)
for(Module module : mModules)
{
eventLoggingModules.add(module);
if(module instanceof EventLogger)
{
eventLoggingModules.add(module);
}
}
}
finally
{
mModuleLock.unlock();
}
for(Module eventLoggingModule : eventLoggingModules)
{
@@ -749,21 +838,30 @@ public class ProcessingChain implements Listener<ChannelEvent>
{
List<Module> recordingModules = new ArrayList<>();
for(Module module : mModules)
mModuleLock.lock();
try
{
if(module instanceof ComplexSamplesWaveRecorder)
for(Module module : mModules)
{
recordingModules.add(module);
}
else if(module instanceof MBECallSequenceRecorder)
{
recordingModules.add(module);
}
else if(module instanceof BinaryRecorder)
{
recordingModules.add(module);
if(module instanceof ComplexSamplesWaveRecorder)
{
recordingModules.add(module);
}
else if(module instanceof MBECallSequenceRecorder)
{
recordingModules.add(module);
}
else if(module instanceof BinaryRecorder)
{
recordingModules.add(module);
}
}
}
finally
{
mModuleLock.unlock();
}
for(Module recordingModule : recordingModules)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -59,6 +59,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasComplete;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
@@ -66,6 +67,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.Capaci
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
@@ -75,13 +77,12 @@ import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
import io.github.dsheirer.util.PacketUtil;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Decoder state for an DMR channel. Maintains the call/data/idle state of the channel and produces events by
@@ -207,6 +208,10 @@ public class DMRDecoderState extends TimeslotDecoderState
{
processPacket((DMRPacketMessage)message);
}
else if(message instanceof UDTShortMessageService sms)
{
processSMS(sms);
}
else if(message instanceof DMRMessage)
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
@@ -269,6 +274,24 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
/**
* Processes a short data message carrying SMS text
* @param sms
*/
private void processSMS(UDTShortMessageService sms)
{
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
DecodeEvent smsEvent = DMRDecodeEvent.builder(sms.getTimestamp())
.eventType(DecodeEventType.SMS)
.eventDescription(DecodeEventType.SMS.toString())
.details("MESSAGE: " + sms.getSMS())
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
.timeslot(sms.getTimeslot())
.build();
broadcast(smsEvent);
}
/**
* Processes a packet message
*/
@@ -1127,6 +1150,12 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(gpsEvent);
}
break;
case FULL_STANDARD_TALKER_ALIAS_COMPLETE:
if(message instanceof TalkerAliasComplete tac && tac.hasTalkerAliasIdentifier())
{
getIdentifierCollection().update(tac.getTalkerAliasIdentifier());
}
break;
}
}

View File

@@ -1,52 +1,56 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
import io.github.dsheirer.module.decode.dmr.message.data.IDLEMessage;
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Aloha;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.Announcement;
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FLCAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.SLCAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.sample.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Processes DMR messages and performs re-assembly of link control fragments
@@ -54,12 +58,13 @@ import java.util.TreeMap;
public class DMRMessageProcessor implements Listener<IMessage>
{
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
private DecodeConfigDMR mConfigDMR;
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
private MBCAssembler mMBCAssembler = new MBCAssembler();
private PacketSequenceAssembler mPacketSequenceAssembler;
private SLCAssembler mSLCAssembler = new SLCAssembler();
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
private Listener<IMessage> mMessageListener;
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
@@ -68,6 +73,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
*/
public DMRMessageProcessor(DecodeConfigDMR config)
{
mConfigDMR = config;
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
{
mTimeslotFrequencyMap.put(timeslotFrequency.getNumber(), timeslotFrequency);
@@ -157,22 +164,43 @@ public class DMRMessageProcessor implements Listener<IMessage>
}
//Packet Sequence Message Assembly ...
if(message instanceof Preamble)
if(message instanceof Preamble preamble)
{
mPacketSequenceAssembler.process((Preamble)message);
mPacketSequenceAssembler.process(preamble);
}
else if(message instanceof PacketSequenceHeader)
else if(message instanceof PacketSequenceHeader header)
{
mPacketSequenceAssembler.process((PacketSequenceHeader)message);
mPacketSequenceAssembler.process(header);
}
else if(message instanceof ProprietaryDataHeader)
else if(message instanceof ProprietaryDataHeader header)
{
mPacketSequenceAssembler.process(((ProprietaryDataHeader)message));
mPacketSequenceAssembler.process(header);
}
else if(message instanceof DataBlock)
else if(message instanceof DataBlock dataBlock)
{
mPacketSequenceAssembler.process((DataBlock)message);
mPacketSequenceAssembler.process(dataBlock);
}
else if(message instanceof UDTHeader header)
{
mPacketSequenceAssembler.process(header);
}
else if((message instanceof IDLEMessage || message instanceof Aloha || message instanceof Announcement) &&
message.getTimeslot() != 0)
{
mPacketSequenceAssembler.dispatchPacketSequence(message.getTimeslot());
}
//Reset talker alias assembler on Idle or Terminator
if(message.isValid() && (message instanceof IDLEMessage || message instanceof Terminator))
{
mTalkerAliasAssembler.reset(message.getTimeslot());
}
}
//Assemble Talker Alias from FLC message fragments (header & blocks 1-3)
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && message.isValid())
{
dispatch(mTalkerAliasAssembler.process(flc));
}
//Now that the message has been (potentially) enriched, dispatch it to the modules
@@ -186,6 +214,26 @@ public class DMRMessageProcessor implements Listener<IMessage>
{
if(mMessageListener != null && message != null)
{
//Configure compressed talkgroups for the DMR talkgroup identifiers in the message.
if(mConfigDMR.isUseCompressedTalkgroups())
{
try
{
for(Identifier identifier: message.getIdentifiers())
{
if(identifier instanceof DMRTalkgroup talkgroup)
{
talkgroup.setCompressed(true);
}
}
}
catch(Exception e)
{
mLog.error("Error applying compressed talkgroup setting to identifier in message: " + message +
" [" + message.getClass() + "]");
}
}
mMessageListener.receive(message);
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2020 Dennis Sheirer, Zhenyu Mao
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr;
@@ -25,7 +24,6 @@ import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
import java.util.ArrayList;
import java.util.List;
@@ -40,6 +38,7 @@ public class DecodeConfigDMR extends DecodeConfiguration
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private boolean mIgnoreDataCalls = true;
private boolean mIgnoreCRCChecksums = false;
private boolean mUseCompressedTalkgroups = false;
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
public DecodeConfigDMR()
@@ -109,6 +108,28 @@ public class DecodeConfigDMR extends DecodeConfiguration
mIgnoreCRCChecksums = ignore;
}
/**
* Indicates if decoder should use compressed talkgroups
*/
@JacksonXmlProperty(isAttribute = true, localName = "use_compressed_talkgroups")
public boolean isUseCompressedTalkgroups()
{
return mUseCompressedTalkgroups;
}
/**
* Sets flag to use compressed talkgroups.
*
* Converts the air interface value to a compressed value described in ETSI 102 361-2, V.2.4.1, Paragraph C2.1.2
* This has been observed in use on Hytera Tier III systems.
* See: https://cwh050.blogspot.com/2021/03/converting-flat-number-to-dmr-id.html?m=1
* @param useCompressedTalkgroups true to use compressed talkgroups.
*/
public void setUseCompressedTalkgroups(boolean useCompressedTalkgroups)
{
mUseCompressedTalkgroups = useCompressedTalkgroups;
}
/**
* Traffic channel pool size.
* @return

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.identifier;
@@ -26,16 +23,33 @@ import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.protocol.Protocol;
/**
* DMR Talkgroup identifier
*/
public class DMRTalkgroup extends TalkgroupIdentifier
{
public DMRTalkgroup(Integer value)
private boolean mCompressed = false;
private int mOriginalValue;
/**
* Constructs an instance
* @param value of the talkgroup
*/
public DMRTalkgroup(int value)
{
super(value, Role.TO);
mOriginalValue = value;
}
public DMRTalkgroup(Integer value, Role role)
/**
* Constructs an instance
* @param value of the talkgroup
* @param role of the talkgroup
*/
public DMRTalkgroup(int value, Role role)
{
super(value, role);
mOriginalValue = value;
}
@Override
@@ -44,6 +58,84 @@ public class DMRTalkgroup extends TalkgroupIdentifier
return Protocol.DMR;
}
@Override
public String toString()
{
if(mCompressed)
{
return super.toString() + "(c)";
}
else
{
return super.toString();
}
}
/**
* Sets the compressed flag for this value.
*
* Talkgroup compression is an alternate format for DMR systems that allows the end user to dial a 7-digit number
* on their mobile keypad using just the digits 0 - 9, to address a talkgroup. The downside to this is that the
* total available talkgroup address space is reduced to just those dialable talkgroups.
*
* @param compressed true when using compressed talkgroup format.
*/
public void setCompressed(boolean compressed)
{
mCompressed = compressed;
if(mCompressed)
{
setValue(getCompressed(mOriginalValue));
}
else
{
setValue(mOriginalValue);
}
}
/**
* Converts the air interface value to a compressed value described in ETSI 102 361-2, V.2.4.1, Paragraph C2.1.2
*
* This has been observed in use on Hytera Tier III systems.
*
* See: https://cwh050.blogspot.com/2021/03/converting-flat-number-to-dmr-id.html?m=1
*
* @param original value to convert
* @return converted value.
*/
public static int getCompressed(int original)
{
int converted = 0;
int units = original / 1_464_100;
converted += (units * 1_000_000);
original -= (units * 1_464_100);
units = original / 146_410;
converted += (units * 100_000);
original -= (units * 146_410);
units = original / 14_641;
converted += (units * 10_000);
original -= (units * 14_641);
units = original / 1_331;
converted += (units * 1_000);
original -= (units * 1_331);
units = original / 121;
converted += (units * 100);
original -= (units * 121);
units = original / 11;
converted += (units * 10);
original -= (units * 11);
converted += original;
return converted;
}
/**
* Creates a DMR TO talkgroup identifier
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,7 +24,12 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAdjacentSiteInformation;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAloha;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko32;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko44;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko47;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraSmsWaitingNotification;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
@@ -49,6 +54,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Aloha;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Clear;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.MoveTSCC;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Protect;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.Acknowledge;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.AcknowledgeStatus;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.RegistrationAccepted;
@@ -78,7 +84,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
import java.util.List;
/**
@@ -200,6 +205,9 @@ public class CSBKMessageFactory
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
csbk = new PrivateVoiceChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_PROTECT:
csbk = new Protect(pattern, message, cach, slotType, timestamp, timeslot);
break;
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
csbk = new TalkgroupDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
break;
@@ -213,6 +221,9 @@ public class CSBKMessageFactory
csbk = new Preamble(pattern, message, cach, slotType, timestamp, timeslot);
break;
case HYTERA_ALOHA:
csbk = new HyteraAloha(pattern, message, cach, slotType, timestamp, timeslot);
break;
case HYTERA_08_ANNOUNCEMENT:
case HYTERA_68_ANNOUNCEMENT:
AnnouncementType announcementType = HyteraAnnouncement.getAnnouncementType(message);
@@ -232,7 +243,18 @@ public class CSBKMessageFactory
case HYTERA_XPT_SITE_STATE:
csbk = new HyteraXPTSiteState(pattern, message, cach, slotType, timestamp, timeslot);
break;
case HYTERA_CSBKO_32:
csbk = new HyteraCsbko32(pattern, message, cach, slotType, timestamp, timeslot);
break;
case HYTERA_CSBKO_44:
csbk = new HyteraCsbko44(pattern, message, cach, slotType, timestamp, timeslot);
break;
case HYTERA_CSBKO_47:
csbk = new HyteraCsbko47(pattern, message, cach, slotType, timestamp, timeslot);
break;
case HYTERA_CSBKO_62:
csbk = new HyteraSmsWaitingNotification(pattern, message, cach, slotType, timestamp, timeslot);
break;
case MOTOROLA_CAPMAX_ALOHA:
csbk = new CapacityMaxAloha(pattern, message, cach, slotType, timestamp, timeslot);
break;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
package io.github.dsheirer.module.decode.dmr.message.data.csbk;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
@@ -50,8 +49,8 @@ public enum Opcode
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD(Vendor.STANDARD, 35, "ACKNOWLEDGE RESPONSE INBOUND PAYLOAD"),
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER(Vendor.STANDARD, 36, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER(Vendor.STANDARD, 37, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_ANNOUNCEMENT(Vendor.STANDARD, 40, "ANNOUNCEMENT"),
STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE(Vendor.STANDARD, 38, "NEGATIVE ACKNOWLEDGE RESPONSE"),
STANDARD_ANNOUNCEMENT(Vendor.STANDARD, 40, "ANNOUNCEMENT"),
STANDARD_MAINTENANCE(Vendor.STANDARD, 42, "MAINTENANCE"),
STANDARD_CLEAR(Vendor.STANDARD, 46, "CLEAR"),
STANDARD_PROTECT(Vendor.STANDARD, 47, "PROTECT"),
@@ -90,12 +89,17 @@ public enum Opcode
MOTOROLA_CAPPLUS_PREAMBLE(Vendor.MOTOROLA_CAPACITY_PLUS, 61, "PREAMBLE"),
MOTOROLA_CAPPLUS_SYSTEM_STATUS(Vendor.MOTOROLA_CAPACITY_PLUS, 62, "SYSTEM STATUS"),
HYTERA_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA XPT SITE STATE"),
HYTERA_CSBKO_32(Vendor.HYTERA_8, 32, "T-III HYTERA 08 CSBKO 32"),
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "T-III 08 ANNOUNCEMENT"),
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "T-III 68 ANNOUNCEMENT"),
HYTERA_CSBKO_44(Vendor.HYTERA_8, 44, "T-III HYTERA 08 CSBKO 44"),
HYTERA_CSBKO_47(Vendor.HYTERA_8, 47, "T-III HYTERA 08 CSBKO 47"),
HYTERA_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA XPT SITE STATE"),
HYTERA_ALOHA(Vendor.HYTERA_68, 25, "HYTERA ALOHA"),
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "T-III 68 ANNOUNCEMENT"),
HYTERA_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA XPT PREAMBLE"), //Opcode 54 and/or 55 - See patent on Hytera call forwarding
HYTERA_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA CSBKO 62"),
//Opcode 54 and/or 55 - See patent on Hytera call forwarding
HYTERA_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA XPT PREAMBLE"),
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");

View File

@@ -0,0 +1,198 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import io.github.dsheirer.module.decode.dmr.message.type.Version;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera DMR Tier III - Aloha Message
*/
public class HyteraAloha extends CSBKMessage
{
//TODO: not sure what bits 16 to 31 are used for ... awaiting documentation.
//TODO: bits 32 to 79 seem to align with the standard aloha.
private static final int[] RESERVED = new int[]{16, 17};
private static final int SITE_TIMESLOT_SYNCHRONIZATION = 18;
private static final int[] VERSION = new int[]{19, 20, 21};
private static final int TIMING_OFFSET = 22;
private static final int ACTIVE_NETWORK_CONNECTION_FLAG = 23;
private static final int[] MASK = new int[]{24, 25, 26, 27, 28};
private static final int[] SERVICE_FUNCTION = new int[]{29, 30};
private static final int[] N_RAND_WAIT = new int[]{31, 32, 33, 34};
private static final int REGISTRATION_REQUIRED_FLAG = 35;
private static final int[] BACKOFF = new int[]{36, 37, 38, 39};
private static final int SYSTEM_IDENTITY_CODE_OFFSET = 40;
private static final int[] RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private SystemIdentityCode mSystemIdentityCode;
private RadioIdentifier mRadioIdentifier;
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public HyteraAloha(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA ALOHA");
if(hasRadioIdentifier())
{
sb.append(" TO:").append(getRadioIdentifier());
}
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
if(hasActiveNetworkConnection())
{
sb.append(" NET-CONNECTED");
}
else
{
sb.append(" NET-DISCONNECTED");
}
sb.append(" SERVICES:").append(getServiceFunction());
sb.append(" ETSI VER:").append(getVersion());
sb.append(" MASK:").append(getMask());
if(getSystemIdentityCode().getPAR().isMultipleControlChannels())
{
sb.append(" ").append(getSystemIdentityCode().getPAR());
}
sb.append(" ").append(getMessage().toHexString());
return sb.toString();
}
/**
* Services provided by the control channel.
*/
public ServiceFunction getServiceFunction()
{
return ServiceFunction.fromValue(getMessage().getInt(SERVICE_FUNCTION));
}
/**
* Mobile subscriber ID masking value. See: 102 361-4 p6.1.3
*/
public int getMask()
{
return getMessage().getInt(MASK);
}
public boolean hasActiveNetworkConnection()
{
return getMessage().get(ACTIVE_NETWORK_CONNECTION_FLAG);
}
/**
* DMR Tier III ETSI 102 361-4 ICD Version Number supported by this system
*/
public Version getVersion()
{
return Version.fromValue(getMessage().getInt(VERSION));
}
/**
* Acknowledged radio identifier
*/
public RadioIdentifier getRadioIdentifier()
{
if(mRadioIdentifier == null)
{
mRadioIdentifier = DMRRadio.createTo(getMessage().getInt(RADIO));
}
return mRadioIdentifier;
}
public boolean hasRadioIdentifier()
{
return getMessage().getInt(RADIO) != 0;
}
/**
* System Identity Code structure
*/
public SystemIdentityCode getSystemIdentityCode()
{
if(mSystemIdentityCode == null)
{
mSystemIdentityCode = new SystemIdentityCode(getMessage(), SYSTEM_IDENTITY_CODE_OFFSET, true);
}
return mSystemIdentityCode;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
if(hasRadioIdentifier())
{
mIdentifiers.add(getRadioIdentifier());
}
mIdentifiers.add(getSystemIdentityCode().getNetwork());
mIdentifiers.add(getSystemIdentityCode().getSite());
}
return mIdentifiers;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,7 +28,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
@@ -78,7 +77,7 @@ public class HyteraAnnouncement extends CSBKMessage
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA ").append(getAnnouncementType());
sb.append(" HYTERA ANNOUNCEMENT-TYPE:").append(getAnnouncementType());
sb.append(" ").append(getSystemIdentityCode().getModel());
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
sb.append(" SITE:").append(getSystemIdentityCode().getSite());

View File

@@ -0,0 +1,137 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera DMR Tier III - CSBKO 32 - Possibly Acknowledge Response
*
* Analysis: ... this was transmitted on the traffic channel while the mobile was at the end of a call, in the terminator
* sequence. Since this comes from the SMS gateway 16776906, I wonder if this is a traffic channel message waiting notification?
*/
public class HyteraCsbko32 extends CSBKMessage
{
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55};
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
private RadioIdentifier mDestinationRadio;
private RadioIdentifier mSourceRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public HyteraCsbko32(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA CSBKO=32 PROBABLE ACKNOWLEDGE RESPONSE");
sb.append(" FM:").append(getSourceRadio());
sb.append(" TO:").append(getDestinationRadio());
sb.append(" UNK1:").append(getUnknown1());
sb.append(" UNK2:").append(getUnknown2());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Addressed (ie TO) radio identifier
* @return radio identifier
*/
public RadioIdentifier getDestinationRadio()
{
if(mDestinationRadio == null)
{
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
}
return mDestinationRadio;
}
public String getUnknown1()
{
return getMessage().getHex(UNKNOWN_1, 2);
}
public String getUnknown2()
{
return getMessage().getHex(UNKNOWN_2, 2);
}
/**
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
}
return mSourceRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getDestinationRadio());
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,80 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.Collections;
import java.util.List;
/**
* Hytera DMR Tier III - CSBKO 44 -
*
* Analysis: ... this was transmitted on the Tier III traffic channel during call termination. This CSBK was
* interleaved with every other Terminator message, starting after the first 2x terminator messages. This continued
* through the final teardown which was 3x Clear messages.
*/
public class HyteraCsbko44 extends CSBKMessage
{
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private List<Identifier> mIdentifiers;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public HyteraCsbko44(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA UNKNOWN CSBKO=44");
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,138 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera DMR Tier III - CSBKO 47 -
*
* Analysis: ... this was transmitted on the traffic channel while the mobile was at the end of a call, in the terminator
* sequence. Since this comes from the SMS gateway 16776906, I wonder if this is a traffic channel message waiting notification?
*/
public class HyteraCsbko47 extends CSBKMessage
{
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55};
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
private RadioIdentifier mDestinationRadio;
private RadioIdentifier mSourceRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public HyteraCsbko47(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA CSBKO=47 ######### UNKNOWN");
sb.append(" FM:").append(getSourceRadio());
sb.append(" TO:").append(getDestinationRadio());
sb.append(" UNK1:").append(getUnknown1());
sb.append(" UNK2:").append(getUnknown2());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Addressed (ie TO) radio identifier
* @return radio identifier
*/
public RadioIdentifier getDestinationRadio()
{
if(mDestinationRadio == null)
{
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
}
return mDestinationRadio;
}
public String getUnknown1()
{
return getMessage().getHex(UNKNOWN_1, 2);
}
public String getUnknown2()
{
return getMessage().getHex(UNKNOWN_2, 2);
}
/**
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
}
return mSourceRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getDestinationRadio());
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -0,0 +1,138 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import java.util.ArrayList;
import java.util.List;
/**
* Hytera DMR Tier III - CSBKO 62 -
*
* Analysis: this message gets sent on timeslot 2 (not the Tier III control timeslot 1) from the SMS gateway (16776906)
* and gets sent to each of the radios involved in the SMS transfer (sender/receiver). I suspect that the network tells
* each of the radios to join the control channel and prepare for the SMS message.
*/
public class HyteraSmsWaitingNotification extends CSBKMessage
{
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 55};
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79};
private List<Identifier> mIdentifiers;
private RadioIdentifier mDestinationRadio;
private RadioIdentifier mSourceRadio;
/**
* Constructs an instance
*
* @param syncPattern for the CSBK
* @param message bits
* @param cach for the DMR burst
* @param slotType for this message
* @param timestamp
* @param timeslot
*/
public HyteraSmsWaitingNotification(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
{
super(syncPattern, message, cach, slotType, timestamp, timeslot);
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
if(!isValid())
{
sb.append("[CRC-ERROR] ");
}
sb.append("CC:").append(getSlotType().getColorCode());
sb.append(" HYTERA PREPARE FOR SMS MESSAGE FOR:").append(getDestinationRadio());
sb.append(" FM:").append(getSourceRadio());
sb.append(" UNK1:").append(getUnknown1());
sb.append(" UNK2:").append(getUnknown2());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
/**
* Addressed (ie TO) radio identifier
* @return radio identifier
*/
public RadioIdentifier getDestinationRadio()
{
if(mDestinationRadio == null)
{
mDestinationRadio = DMRRadio.createTo(getMessage().getInt(DESTINATION_RADIO));
}
return mDestinationRadio;
}
public String getUnknown1()
{
return getMessage().getHex(UNKNOWN_1, 2);
}
public String getUnknown2()
{
return getMessage().getHex(UNKNOWN_2, 2);
}
/**
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
*/
public RadioIdentifier getSourceRadio()
{
if(mSourceRadio == null)
{
mSourceRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_RADIO));
}
return mSourceRadio;
}
@Override
public List<Identifier> getIdentifiers()
{
if(mIdentifiers == null)
{
mIdentifiers = new ArrayList<>();
mIdentifiers.add(getDestinationRadio());
mIdentifiers.add(getSourceRadio());
}
return mIdentifiers;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,7 +37,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.ArrayList;
import java.util.List;
@@ -118,6 +117,7 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
sb.append(" CLEAR - RETURN TO ").append(getMoveToChannel());
sb.append(" FM:").append(getSourceRadio());
sb.append(" TO:").append(getDestinationId());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,7 +32,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceAccessPoint;
import io.github.dsheirer.module.decode.dmr.message.type.UnifiedDataTransportFormat;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import java.util.ArrayList;
import java.util.List;
@@ -84,13 +83,13 @@ public class UDTHeader extends DataHeader
sb.append(" [CRC ERROR]");
}
sb.append(" UDT HEADER");
if(isSupplementaryData())
if(isShortData())
{
sb.append(" SUPPLEMENTARY DATA");
sb.append(" SHORT DATA");
}
else
{
sb.append(" SHORT DATA");
sb.append(" SUPPLEMENTARY DATA");
}
sb.append(" FM:").append(getSourceLLID());
sb.append(" TO:").append(getDestinationLLID());
@@ -106,7 +105,7 @@ public class UDTHeader extends DataHeader
sb.append(" OPCODE:").append(getOpcode());
}
sb.append(" PAD NIBBLES:").append(getPadNibbleCount());
sb.append(" ").append(getServiceAccessPoint());
sb.append(" SAP:").append(getServiceAccessPoint());
return sb.toString();
}
@@ -121,7 +120,15 @@ public class UDTHeader extends DataHeader
}
/**
* Indicates if the header contains supplementary data (true) or short data (false)
* Indicates if the header is for a short data sequence
*/
public boolean isShortData()
{
return !isSupplementaryData();
}
/**
* Indicates if the header is for supplementary data
*/
public boolean isSupplementaryData()
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,10 +21,15 @@ package io.github.dsheirer.module.decode.dmr.message.data.lc;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.edac.CRCDMR;
import io.github.dsheirer.edac.Checksum_5_DMR;
import io.github.dsheirer.edac.ReedSolomon_12_9_4_DMR;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasBlock1;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasBlock2;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasBlock3;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasHeader;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TerminatorData;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMessage;
@@ -71,9 +76,22 @@ public class LCMessageFactory
throw new IllegalArgumentException("Message cannot be null");
}
//RS(12,9,4) can correct up to floor(4/2) = 1 bit error.
boolean corrected = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message,
isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
boolean valid = true;
if(message.size() == 77)
{
valid = Checksum_5_DMR.isValid(message);
}
else if(message.size() == 96)
{
//RS(12,9,4) can correct up to floor(4/2) = 1 bit error.
valid = REED_SOLOMON_12_9_4_DMR.correctFullLinkControl(message,
isTerminator ? TERMINATOR_LINK_CONTROL_CRC_MASK : VOICE_LINK_CONTROL_CRC_MASK);
}
else
{
mLog.warn("Unrecognized Link Control Message Size: " + message.size());
}
LCOpcode opcode = FullLCMessage.getOpcode(message);
@@ -110,15 +128,24 @@ public class LCMessageFactory
flc = new HyteraTerminator(message, timestamp, timeslot);
break;
case FULL_STANDARD_TALKER_ALIAS_HEADER:
flc = new TalkerAliasHeader(message, timestamp, timeslot);
break;
case FULL_STANDARD_TALKER_ALIAS_BLOCK_1:
flc = new TalkerAliasBlock1(message, timestamp, timeslot);
break;
case FULL_STANDARD_TALKER_ALIAS_BLOCK_2:
flc = new TalkerAliasBlock2(message, timestamp, timeslot);
break;
case FULL_STANDARD_TALKER_ALIAS_BLOCK_3:
flc = new TalkerAliasBlock3(message, timestamp, timeslot);
break;
default:
flc = new UnknownFullLCMessage(message, timestamp, timeslot);
break;
}
flc.setValid(corrected);
flc.setValid(valid);
return flc;
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,8 +20,8 @@
package io.github.dsheirer.module.decode.dmr.message.data.lc;
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.TreeMap;
@@ -39,6 +39,7 @@ public enum LCOpcode
FULL_STANDARD_TALKER_ALIAS_BLOCK_1(Vendor.STANDARD,true, 5, "TALKER ALIAS BLOCK 1"),
FULL_STANDARD_TALKER_ALIAS_BLOCK_2(Vendor.STANDARD,true, 6, "TALKER ALIAS BLOCK 2"),
FULL_STANDARD_TALKER_ALIAS_BLOCK_3(Vendor.STANDARD,true, 7, "TALKER ALIAS BLOCK 3"),
FULL_STANDARD_TALKER_ALIAS_COMPLETE(Vendor.STANDARD, true, -1, "TALKER ALIAS COMPLETE"), //Not part of ICD
FULL_STANDARD_GPS_INFO(Vendor.STANDARD,true, 8, "GPS INFO"),
FULL_STANDARD_TERMINATOR_DATA(Vendor.STANDARD, true, 48, "TERMINATOR DATA"),
FULL_STANDARD_UNKNOWN(Vendor.STANDARD,true, -1, "FULL UNKNOWN"),
@@ -67,6 +68,9 @@ public enum LCOpcode
SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "SHORT UNKNOWN");
private static final EnumSet<LCOpcode> TALKER_ALIAS_OPCODES = EnumSet.of(FULL_STANDARD_TALKER_ALIAS_HEADER,
FULL_STANDARD_TALKER_ALIAS_BLOCK_1, FULL_STANDARD_TALKER_ALIAS_BLOCK_2, FULL_STANDARD_TALKER_ALIAS_BLOCK_3);
private Vendor mVendor;
private boolean mFull;
private int mValue;
@@ -111,6 +115,14 @@ public enum LCOpcode
return !mFull;
}
/**
* Indicates if this entry is one of the four talker alias opcodes.
*/
public boolean isTalkerAliasOpcode()
{
return TALKER_ALIAS_OPCODES.contains(this);
}
public int getValue()
{
return mValue;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -93,7 +93,7 @@ public class FLCAssembler
}
/**
* Dispatches a fully-received messsage
* Dispatches a fully-received message
*/
private FullLCMessage dispatch()
{
@@ -133,6 +133,7 @@ public class FLCAssembler
for(int row = 0; row < 8; row++)
{
int rowErrorCount = Hamming16.checkAndCorrect(descrambled, row * 16);
errorCount += rowErrorCount;
if(errorCount > 1)
@@ -179,6 +180,9 @@ public class FLCAssembler
{
extractedMessage.set(pointer++, descrambled.get(row * 16 + column));
}
//Transfer the checksum parity bit from this row
extractedMessage.set(70 + row, descrambled.get(row * 16 + 10));
}
extractedMessage.setCorrectedBitCount(errorCount);

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -99,4 +99,13 @@ public abstract class FullLCMessage extends LCMessage
{
return getMessage().get(ENCRYPTION_FLAG);
}
/**
* Indicates if the reserved bit is set.
* @return true if set
*/
public boolean isReservedBitSet()
{
return getMessage().get(RESERVED);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -25,7 +25,6 @@ import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import java.util.ArrayList;
import java.util.List;
@@ -69,6 +68,7 @@ public class GroupVoiceChannelUser extends AbstractVoiceChannelUser
sb.append(getRadio());
sb.append(" TO:").append(getTalkgroup());
sb.append(" ").append(getServiceOptions());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}

View File

@@ -0,0 +1,224 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Aggregates a sequence of talker alias full link control messages to reconstruct the talker alias.
*/
public class TalkerAliasAssembler
{
//Timeslot 1 fragments
private TalkerAliasHeader mHeaderTs1;
private TalkerAliasBlock1 mBlock1Ts1;
private TalkerAliasBlock2 mBlock2Ts1;
private TalkerAliasBlock3 mBlock3Ts1;
//Timeslot 2 fragments
private TalkerAliasHeader mHeaderTs2;
private TalkerAliasBlock1 mBlock1Ts2;
private TalkerAliasBlock2 mBlock2Ts2;
private TalkerAliasBlock3 mBlock3Ts2;
//Flags indicating if we have any non-null fragments for the specified timeslot to support resetting.
private boolean mEmptyTs1 = true;
private boolean mEmptyTs2 = true;
/**
* Constructs an instance.
*/
public TalkerAliasAssembler()
{
}
/**
* Resets the contents of either timeslot 1 or 2 when a terminator or idle message is received.
* @param timeslot 1 or 2
*/
public void reset(int timeslot)
{
if(timeslot == 1 && !mEmptyTs1)
{
mHeaderTs1 = null;
mBlock1Ts1 = null;
mBlock2Ts1 = null;
mBlock3Ts1 = null;
mEmptyTs1 = true;
}
else if(timeslot == 2 && !mEmptyTs2)
{
mHeaderTs2 = null;
mBlock1Ts2 = null;
mBlock2Ts2 = null;
mBlock3Ts2 = null;
mEmptyTs2 = true;
}
}
/**
* Processes the FLC talker alias header or block and returns a fully assembled alias when all fragments are received.
* @param message containing FLC talker alias header or blocks 1-3
* @return fully assembled talker alias, if available, or null.
*/
public TalkerAliasComplete process(FullLCMessage message)
{
switch(message.getOpcode())
{
case FULL_STANDARD_TALKER_ALIAS_HEADER:
if(message instanceof TalkerAliasHeader tah)
{
if(message.getTimeslot() == 1)
{
mHeaderTs1 = tah;
mEmptyTs1 = false;
return assemble(1);
}
else
{
mHeaderTs2 = tah;
mEmptyTs2 = false;
return assemble(2);
}
}
case FULL_STANDARD_TALKER_ALIAS_BLOCK_1:
if(message instanceof TalkerAliasBlock1 tab1)
{
if(message.getTimeslot() == 1)
{
mBlock1Ts1 = tab1;
mEmptyTs1 = false;
return assemble(1);
}
else
{
mBlock1Ts2 = tab1;
mEmptyTs2 = false;
return assemble(2);
}
}
case FULL_STANDARD_TALKER_ALIAS_BLOCK_2:
if(message instanceof TalkerAliasBlock2 tab2)
{
if(message.getTimeslot() == 1)
{
mBlock2Ts1 = tab2;
mEmptyTs1 = false;
return assemble(1);
}
else
{
mBlock2Ts2 = tab2;
mEmptyTs2 = false;
return assemble(2);
}
}
case FULL_STANDARD_TALKER_ALIAS_BLOCK_3:
if(message instanceof TalkerAliasBlock3 tab3)
{
if(message.getTimeslot() == 1)
{
mBlock3Ts1 = tab3;
mEmptyTs1 = false;
return assemble(1);
}
else
{
mBlock3Ts2 = tab3;
mEmptyTs2 = false;
return assemble(2);
}
}
}
return null;
}
/**
* Assembles the complete alias.
* @param timeslot to assemble.
* @return assembled alias or null.
*/
private TalkerAliasComplete assemble(int timeslot)
{
if(timeslot == 1)
{
return assemble(mHeaderTs1, mBlock1Ts1, mBlock2Ts1, mBlock3Ts1);
}
else
{
return assemble(mHeaderTs2, mBlock1Ts2, mBlock2Ts2, mBlock3Ts2);
}
}
/**
* Assembles a complete alias from the header and block fragments.
* @param header of the alias
* @param block1 of the alias, optional
* @param block2 of the alias, optional
* @param block3 of the alias, optional
* @return assembled alias or null.
*/
private TalkerAliasComplete assemble(TalkerAliasHeader header,
TalkerAliasBlock1 block1,
TalkerAliasBlock2 block2,
TalkerAliasBlock3 block3)
{
if(header != null)
{
int bitLength = header.getTotalBitLength();
if(1 <= bitLength && bitLength <= 49)
{
return new TalkerAliasComplete(header.getPayloadFragment(), header.getFormat(), header.getCharacterLength(),
header.getTimestamp(), header.getTimeslot());
}
else if(50 <= bitLength && bitLength <= 105 && block1 != null)
{
CorrectedBinaryMessage message = new CorrectedBinaryMessage(105);
message.load(0, header.getPayloadFragment());
message.load(49, block1.getPayloadFragment());
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
header.getTimestamp(), header.getTimeslot());
}
else if(106 <= bitLength && bitLength <= 161 && block1 != null && block2 != null)
{
CorrectedBinaryMessage message = new CorrectedBinaryMessage(161);
message.load(0, header.getPayloadFragment());
message.load(49, block1.getPayloadFragment());
message.load(105, block2.getPayloadFragment());
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
header.getTimestamp(), header.getTimeslot());
}
else if(162 <= bitLength && bitLength <= 217 && block1 != null && block2 != null && block3 != null)
{
CorrectedBinaryMessage message = new CorrectedBinaryMessage(217);
message.load(0, header.getPayloadFragment());
message.load(49, block1.getPayloadFragment());
message.load(105, block2.getPayloadFragment());
message.load(161, block3.getPayloadFragment());
return new TalkerAliasComplete(message, header.getFormat(), header.getCharacterLength(),
header.getTimestamp(), header.getTimeslot());
}
}
return null;
}
}

View File

@@ -0,0 +1,70 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import java.util.Collections;
import java.util.List;
/**
* Talker alias continuation block 1.
*/
public class TalkerAliasBlock1 extends FullLCMessage
{
private static final int PAYLOAD_START = 16;
private static final int PAYLOAD_END = 72;
/**
* Constructs an instance
*
* @param message for link control payload
* @param timestamp for the message
* @param timeslot where the message was transmitted
*/
public TalkerAliasBlock1(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
/**
* Payload fragment carried by the header.
* @return payload fragment.
*/
public CorrectedBinaryMessage getPayloadFragment()
{
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("FLC TALKER ALIAS BLOCK 1");
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -0,0 +1,70 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import java.util.Collections;
import java.util.List;
/**
* Talker alias continuation block 2.
*/
public class TalkerAliasBlock2 extends FullLCMessage
{
private static final int PAYLOAD_START = 16;
private static final int PAYLOAD_END = 72;
/**
* Constructs an instance
*
* @param message for link control payload
* @param timestamp for the message
* @param timeslot where the message was transmitted
*/
public TalkerAliasBlock2(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
/**
* Payload fragment carried by the header.
* @return payload fragment.
*/
public CorrectedBinaryMessage getPayloadFragment()
{
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("FLC TALKER ALIAS BLOCK 2");
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -0,0 +1,70 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import java.util.Collections;
import java.util.List;
/**
* Talker alias continuation block 3.
*/
public class TalkerAliasBlock3 extends FullLCMessage
{
private static final int PAYLOAD_START = 16;
private static final int PAYLOAD_END = 72;
/**
* Constructs an instance
*
* @param message for link control payload
* @param timestamp for the message
* @param timeslot where the message was transmitted
*/
public TalkerAliasBlock3(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
/**
* Payload fragment carried by the header.
* @return payload fragment.
*/
public CorrectedBinaryMessage getPayloadFragment()
{
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("FLC TALKER ALIAS BLOCK 3");
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -0,0 +1,180 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
import io.github.dsheirer.module.decode.dmr.message.type.TalkerAliasDataFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Complete talker alias assembled from talker alias header and 0 or more talker alias blocks.
*/
public class TalkerAliasComplete extends FullLCMessage
{
private static final Logger LOGGER = LoggerFactory.getLogger(TalkerAliasComplete.class);
private TalkerAliasDataFormat mDataFormat;
private int mCharacterCount;
private String mAlias;
private DmrTalkerAliasIdentifier mTalkerAliasIdentifier;
/**
* Constructs an instance
*
* @param message for link control payload
* @param timestamp for the header
* @param timeslot for the header and blocks
*/
public TalkerAliasComplete(CorrectedBinaryMessage message, TalkerAliasDataFormat format, int characterCount,
long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
mDataFormat = format;
mCharacterCount = characterCount;
}
@Override
public LCOpcode getOpcode()
{
return LCOpcode.FULL_STANDARD_TALKER_ALIAS_COMPLETE;
}
@Override
public int getOpcodeValue()
{
return getOpcode().getValue();
}
/**
* Alias value
* @return alias value if it can be decoded or empty string.
*/
public String getAliasValue()
{
if(mAlias == null)
{
switch(mDataFormat)
{
//Note: there are 31 possible 7-bit characters, 27 possible 8-bit characters and 13.5 possible 16-bit
//characters. We check each format to ensure character count is at least one and no more than max.
case BIT_7:
if(1 <= mCharacterCount && mCharacterCount <= 31)
{
mAlias = getMessage().parseISO7(0, mCharacterCount);
}
break;
case BIT_8:
if(1 <= mCharacterCount && mCharacterCount <= 27)
{
mAlias = getMessage().parseISO8(1, mCharacterCount);
}
break;
case UTF_8:
if(1 <= mCharacterCount && mCharacterCount <= 27)
{
try
{
mAlias = getMessage().parseUTF8(1, mCharacterCount);
}
catch(Exception e)
{
LOGGER.info("Error decoding talker alias as UTF-8: " + e.getLocalizedMessage());
}
}
break;
case UNICODE_UTF_16_BE:
if(1 <= mCharacterCount && mCharacterCount <= 13)
{
mAlias = getMessage().parseUnicode(1, mCharacterCount);
}
break;
}
//If we couldn't decode, set it to empty so that we don't reprocess every time.
if(mAlias == null)
{
mAlias = "";
}
}
return mAlias;
}
/**
* Talker Alias identifier.
* @return identifier or null if the alias string is null or empty, meaning it could not be decoded.
*/
public DmrTalkerAliasIdentifier getTalkerAliasIdentifier()
{
if(mTalkerAliasIdentifier == null && getAliasValue() != null && !getAliasValue().isEmpty())
{
mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(getAliasValue());
}
return mTalkerAliasIdentifier;
}
/**
* Indicates if the talker alias identifier is non-null.
* @return true if non-null.
*/
public boolean hasTalkerAliasIdentifier()
{
return getTalkerAliasIdentifier() != null;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("FLC TALKER ALIAS:");
if(hasTalkerAliasIdentifier())
{
sb.append(getTalkerAliasIdentifier());
}
else
{
sb.append("(empty/error)");
}
return sb.toString();
}
@Override
public List<Identifier> getIdentifiers()
{
if(hasTalkerAliasIdentifier())
{
List<Identifier> identifiers = new ArrayList<>();
identifiers.add(getTalkerAliasIdentifier());
return identifiers;
}
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,109 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.full;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.message.type.TalkerAliasDataFormat;
import java.util.Collections;
import java.util.List;
/**
* Talker alias header with format and length description and a fragment of the alias data.
*/
public class TalkerAliasHeader extends FullLCMessage
{
private static final int[] FORMAT = new int[]{16, 17};
private static final int[] LENGTH = new int[]{18, 19, 20, 21, 22};
private static final int PAYLOAD_START = 23;
private static final int PAYLOAD_END = 72;
/**
* Constructs an instance
*
* @param message for link control payload
* @param timestamp for the message
* @param timeslot where the message was transmitted
*/
public TalkerAliasHeader(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
/**
* Formatting used for the alias characters.
* @return format
*/
public TalkerAliasDataFormat getFormat()
{
return TalkerAliasDataFormat.fromValue(getMessage().getInt(FORMAT));
}
/**
* Length of the alias in characters.
* @return length in characters.
*/
public int getCharacterLength()
{
return getMessage().getInt(LENGTH);
}
/**
* Total bit length of the alias characters.
*/
public int getTotalBitLength()
{
int total = getFormat().getBitsPerCharacter() * getCharacterLength();
//Max payload bit length is 217 (49 + 56 + 56 + 56)
if(total > 217)
{
return 217;
}
return total;
}
/**
* Payload fragment carried by the header.
* @return payload fragment.
*/
public CorrectedBinaryMessage getPayloadFragment()
{
return getMessage().getSubMessage(PAYLOAD_START, PAYLOAD_END);
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("FLC TALKER ALIAS HEADER");
sb.append(" FORMAT:").append(getFormat());
sb.append(" CHARACTERS:").append(getCharacterLength());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,20 +28,22 @@ import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus - Group Voice Channel User
*/
public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser implements ITimeslotFrequencyReceiver
public class CapacityPlusGroupVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
{
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] CAPACITY_PLUS_GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] CONVENTIONAL_GROUP_ADDRESS = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] VOICE_CHANNEL_REPEATER = new int[]{51, 52, 53, 54};
private static final int[] VOICE_CHANNEL_TIMESLOT = new int[]{55};
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] CAPACITY_PLUS_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] CONVENTIONAL_SOURCE_ADDRESS = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] UNKNOWN = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadio;
@@ -69,20 +71,33 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
sb.append("[CRC-ERROR] ");
}
sb.append("FLC MOTOROLA CAP+ GROUP VOICE CHANNEL USER FM:");
sb.append(getRadio());
sb.append(" TO:").append(getTalkgroup());
sb.append(" ON CHANNEL:");
if(hasVoiceChannel())
if(isEncrypted())
{
sb.append(getVoiceChannel());
sb.append(" ENCRYPTED");
}
if(isReservedBitSet())
{
sb.append(" RESERVED-BIT");
}
if(getServiceOptions().isCapacityPlus())
{
sb.append("FLC MOTOROLA CAP+ GROUP VOICE CHANNEL USER");
}
else
{
sb.append("--");
sb.append("FLC MOTOROLA CONV/IP SITE GROUP VOICE CHANNEL USER");
}
sb.append(" FM:").append(getRadio());
sb.append(" TO:").append(getTalkgroup());
if(hasVoiceChannel())
{
sb.append(" ON CHANNEL:");
sb.append(getVoiceChannel());
}
sb.append(" UNK:").append(getUnknown());
sb.append(" ").append(getServiceOptions());
sb.append(" MSG:").append(getMessage().toHexString());
return sb.toString();
@@ -119,6 +134,7 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
/**
* Rest timeslot
*
* @return
*/
public int getVoiceChannelTimeslot()
@@ -131,7 +147,7 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
*/
public boolean hasVoiceChannel()
{
return getVoiceChannelRepeater() != 0;
return getServiceOptions().isCapacityPlus() && getVoiceChannelRepeater() != 0;
}
/**
@@ -141,7 +157,14 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
{
if(mRadio == null)
{
mRadio = DMRRadio.createFrom(getMessage().getInt(SOURCE_ADDRESS));
if(getServiceOptions().isCapacityPlus())
{
mRadio = DMRRadio.createFrom(getMessage().getInt(CAPACITY_PLUS_SOURCE_ADDRESS));
}
else
{
mRadio = DMRRadio.createFrom(getMessage().getInt(CONVENTIONAL_SOURCE_ADDRESS));
}
}
return mRadio;
@@ -154,7 +177,14 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
{
if(mTalkgroup == null)
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(GROUP_ADDRESS));
if(getServiceOptions().isCapacityPlus())
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CAPACITY_PLUS_GROUP_ADDRESS));
}
else
{
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(CONVENTIONAL_GROUP_ADDRESS));
}
}
return mTalkgroup;
@@ -189,12 +219,13 @@ public class CapacityPlusGroupVoiceChannelUser extends AbstractVoiceChannelUser
/**
* Applies the LSN to frequency map to the rest channel.
*
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(getVoiceChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
{

View File

@@ -0,0 +1,57 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
import io.github.dsheirer.module.decode.dmr.message.type.CapacityPlusServiceOptions;
/**
* Any Capacity Plus Voice Channel User link control message that contains vendor-specific service options.
*/
public abstract class CapacityPlusVoiceChannelUser extends FullLCMessage
{
private static final int[] SERVICE_OPTIONS = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private CapacityPlusServiceOptions mServiceOptions;
/**
* Constructs an instance.
*
* @param message for the link control payload
*/
public CapacityPlusVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
{
super(message, timestamp, timeslot);
}
/**
* Service options for the call
*/
public CapacityPlusServiceOptions getServiceOptions()
{
if(mServiceOptions == null)
{
mServiceOptions = new CapacityPlusServiceOptions(getMessage().getInt(SERVICE_OPTIONS));
}
return mServiceOptions;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,22 +28,20 @@ import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus - Wide Area (Multi-Site) Voice Channel User
*/
public class CapacityPlusWideAreaVoiceChannelUser extends AbstractVoiceChannelUser implements ITimeslotFrequencyReceiver
public class CapacityPlusWideAreaVoiceChannelUser extends CapacityPlusVoiceChannelUser implements ITimeslotFrequencyReceiver
{
private static final int[] UNKNOWN_1 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47};
private static final int[] REST_REPEATER = new int[]{51, 52, 53, 54};
private static final int[] REST_TIMESLOT = new int[]{55};
private static final int[] SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71};
private static final int[] UNKNOWN_2 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private static final int[] UNKNOWN_3 = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
@@ -70,6 +68,16 @@ public class CapacityPlusWideAreaVoiceChannelUser extends AbstractVoiceChannelUs
sb.append("[CRC-ERROR] ");
}
if(isEncrypted())
{
sb.append(" *ENCRYPTED*");
}
if(isReservedBitSet())
{
sb.append(" *RESERVED-BIT*");
}
sb.append("FLC MOTOROLA CAP+ WIDE-AREA VOICE CHANNEL USER FM:");
sb.append(getRadio());
sb.append(" TO:").append(getTalkgroup());
@@ -102,7 +110,7 @@ public class CapacityPlusWideAreaVoiceChannelUser extends AbstractVoiceChannelUs
*/
public int getUnknown2()
{
return getMessage().getInt(UNKNOWN_2);
return getMessage().getInt(UNKNOWN_3);
}
/**

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,7 +23,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import java.util.ArrayList;
import java.util.List;
@@ -34,6 +34,7 @@ public class PacketSequence
{
private int mTimeslot;
private PacketSequenceHeader mPacketSequenceHeader;
private UDTHeader mUDTHeader;
private ProprietaryDataHeader mProprietaryDataHeader;
private List<Preamble> mPreambles = new ArrayList<>();
private List<DataBlock> mDataBlocks = new ArrayList<>();
@@ -51,7 +52,11 @@ public class PacketSequence
sb.append(" TS:").append(getTimeslot());
if(mPacketSequenceHeader != null)
{
sb.append(mPacketSequenceHeader.toString());
sb.append(mPacketSequenceHeader);
}
else if(mUDTHeader != null)
{
sb.append(mUDTHeader);
}
sb.append(" HEADER:").append(hasPacketSequenceHeader());
sb.append(" PROPRIETARY HEADER:").append(hasProprietaryDataHeader());
@@ -97,6 +102,15 @@ public class PacketSequence
mPacketSequenceHeader = header;
}
/**
* Unified Data Transport (UDT) header
* @param header for a UDT sequence
*/
public void setUDTHeader(UDTHeader header)
{
mUDTHeader = header;
}
/**
* Proprietary data header
*/
@@ -138,6 +152,23 @@ public class PacketSequence
return mPacketSequenceHeader != null;
}
/**
* UDT header
* @return header or null.
*/
public UDTHeader getUDTHeader()
{
return mUDTHeader;
}
/**
* Indicates if this packet sequence has a UDT header
*/
public boolean hasUDTHeader()
{
return getUDTHeader() != null;
}
/**
* Secondary/Proprietary packet sequence header
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2021 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import io.github.dsheirer.sample.Listener;
/**
@@ -70,7 +71,7 @@ public class PacketSequenceAssembler
}
/**
* Sets the listener to receive packet packet sequence messages
* Sets the listener to receive packet sequence messages
* @param listener to receive messages
*/
public void setMessageListener(Listener<IMessage> listener)
@@ -82,11 +83,11 @@ public class PacketSequenceAssembler
* Dispatches the packet sequence for the specified timeslot and sets the sequence to null.
* @param timeslot to dispatch
*/
private void dispatchPacketSequence(int timeslot)
public void dispatchPacketSequence(int timeslot)
{
PacketSequence packetSequence = (timeslot == 1 ? mTimeslot1Sequence : mTimeslot2Sequence);
if(mMessageListener != null && packetSequence != null && packetSequence.isComplete())
if(packetSequence != null && mMessageListener != null)
{
IMessage message = PacketSequenceMessageFactory.create(packetSequence);
@@ -154,6 +155,27 @@ public class PacketSequenceAssembler
packetSequence.setPacketSequenceHeader(header);
}
/**
* Process a UDT header.
* @param header
*/
public void process(UDTHeader header)
{
int timeslot = header.getTimeslot();
PacketSequence packetSequence = getPacketSequence(timeslot);
//If we already have headers or data blocks for the current sequence, then this is a new sequence
if(packetSequence.hasPacketSequenceHeader() || packetSequence.hasProprietaryDataHeader() ||
packetSequence.hasDataBlocks())
{
dispatchPacketSequence(timeslot);
packetSequence = getPacketSequence(timeslot);
}
packetSequence.setUDTHeader(header);
}
/**
* Processes a proprietary packet sequence header.
*/

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.type.ApplicationType;
@@ -35,11 +36,10 @@ 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.lrrp.LRRPPacket;
import io.github.dsheirer.module.decode.ip.xcmp.XCMPPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Converts a DMR packet sequence into a message
@@ -55,30 +55,73 @@ public class PacketSequenceMessageFactory
*/
public static IMessage create(PacketSequence packetSequence)
{
if(packetSequence != null && packetSequence.isComplete())
if(packetSequence != null)
{
PacketSequenceHeader primaryHeader = packetSequence.getPacketSequenceHeader();
CorrectedBinaryMessage packet = getPacket(packetSequence, primaryHeader.isConfirmedData());
if(packet != null)
if(packetSequence.hasPacketSequenceHeader())
{
switch(primaryHeader.getServiceAccessPoint())
PacketSequenceHeader primaryHeader = packetSequence.getPacketSequenceHeader();
CorrectedBinaryMessage packet = getPacket(packetSequence, primaryHeader.isConfirmedData());
if(packet != null)
{
case IP_PACKET_DATA:
return createIPPacketData(packetSequence, packet);
case PROPRIETARY_DATA:
return createProprietary(packetSequence, packet);
case SHORT_DATA:
return createDefinedShortData(packetSequence, packet);
default:
mLog.info("Unknown Packet SAP: " + primaryHeader.getServiceAccessPoint() + " - returning unknown packet");
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
switch(primaryHeader.getServiceAccessPoint())
{
case IP_PACKET_DATA:
return createIPPacketData(packetSequence, packet);
case PROPRIETARY_DATA:
return createProprietary(packetSequence, packet);
case SHORT_DATA:
return createDefinedShortData(packetSequence, packet);
default:
mLog.info("Unknown Packet SAP: " + primaryHeader.getServiceAccessPoint() + " - returning unknown packet");
return new DMRPacketMessage(packetSequence, new UnknownPacket(packet, 0), packet,
packetSequence.getTimeslot(), packetSequence.getPacketSequenceHeader().getTimestamp());
}
}
}
else if(packetSequence.hasUDTHeader() && packetSequence.hasDataBlocks())
{
UDTHeader header = packetSequence.getUDTHeader();
if(header.isShortData())
{
CorrectedBinaryMessage payload = getUnconfirmedPayload(packetSequence.getDataBlocks());
return new UDTShortMessageService(header, payload);
}
}
}
//TODO: NOTE: Don't create a DMRPacketMessage unless we have a)Packet and b)Packet Sequence Header
return null;
}
/**
* Assembles the unconfirmed payload bytes from each of the data blocks into a contiguous binary message.
* @param dataBlocks to assemble
* @return assembled unconfirmed payload.
*/
public static CorrectedBinaryMessage getUnconfirmedPayload(List<DataBlock> dataBlocks)
{
if(!dataBlocks.isEmpty())
{
int length = 0;
List<CorrectedBinaryMessage> fragments = new ArrayList<>();
for(DataBlock dataBlock: dataBlocks)
{
CorrectedBinaryMessage fragment = dataBlock.getUnConfirmedPayload();
length += fragment.size();
fragments.add(fragment);
}
CorrectedBinaryMessage combined = new CorrectedBinaryMessage(length);
int pointer = 0;
for(CorrectedBinaryMessage fragment: fragments)
{
combined.load(pointer, fragment);
pointer += fragment.size();
}
return combined;
}
return null;
}

View File

@@ -0,0 +1,188 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.data.packet;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Short Message Service (SMS) using Unified Data Transport
*/
public class UDTShortMessageService extends DMRMessage
{
private static final Logger LOGGER = LoggerFactory.getLogger(UDTShortMessageService.class);
private UDTHeader mHeader;
private String mSMS;
/**
* Constructs an instance
*
* @param header for the sequence
* @param payload from the data blocks
*/
public UDTShortMessageService(UDTHeader header, CorrectedBinaryMessage payload)
{
super(payload, header.getTimestamp(), header.getTimeslot());
mHeader = header;
}
@Override
public List<Identifier> getIdentifiers()
{
return mHeader.getIdentifiers();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("CC:").append(mHeader.getSlotType().getColorCode());
sb.append(" SMS MESSAGE:").append(getSMS());
sb.append(" FROM:").append(mHeader.getSourceLLID());
sb.append(" TO:").append(mHeader.getDestinationLLID());
sb.append(" HEX:").append(getMessage().toHexString());
return sb.toString();
}
public String getSMS()
{
if(mSMS == null)
{
switch(mHeader.getFormat())
{
case UNICODE_16:
mSMS = parseUnicodePayload();
break;
case ASCII_7:
mSMS = parseISO7Payload();
break;
case ASCII_8:
mSMS = parseISO8Payload();
break;
case BCD_4_BITS:
mSMS = parseBCD4Payload();
break;
case BINARY:
case MOBILE_SUBSCRIBER_OR_TALKGROUP_ADDRESS:
case NMEA_GPS_LOCATION_CODED:
case IP_ADDRESS:
case VENDOR_PROPRIETARY_8:
case VENDOR_PROPRIETARY_9:
case MIXED_FORMAT:
case UNKNOWN:
LOGGER.warn("Unrecognized UDT Short Data Format: " + mHeader.getFormat() +
" - Please send this to the sdrtrunk developer - Hex:" + getMessage().toHexString() +
" As String:" + new String(getMessage().getBytes()));
mSMS = "Error:" + new String(getMessage().getBytes());
break;
}
}
if(mSMS == null)
{
mSMS = "error - unrecognized format: " + mHeader.getFormat();
}
return mSMS;
}
/**
* Parses a unicode 16-bit payload from the message.
* @return parsed message
*/
private String parseUnicodePayload()
{
int length = getMessage().size();
length -= (mHeader.getPadNibbleCount() * 4);
length -= 16; //Exclude the dangling 32-bit CRC
length -= 16; //Exclude the dangling 0x0006 ACKNOWLEDGE character
if(length > 16)
{
return getMessage().parseUnicode(0, (length / 16));
}
else
{
return "(insufficient data)";
}
}
/**
* Parses an ISO-7 payload from the message.
* @return parsed message
*/
private String parseISO7Payload()
{
int length = getMessage().size();
length -= (mHeader.getPadNibbleCount() * 4);
if(length > 7)
{
return getMessage().parseISO7(0, (length / 7));
}
else
{
return "(insufficient data)";
}
}
/**
* Parses an ISO-8 payload from the message.
* @return parsed message
*/
private String parseISO8Payload()
{
int length = getMessage().size();
length -= (mHeader.getPadNibbleCount() * 4);
if(length > 8)
{
return getMessage().parseISO8(0, (length / 8));
}
else
{
return "(insufficient data)";
}
}
/**
* Parses a BCD 4-bit payload from the message.
* @return parsed message
*/
private String parseBCD4Payload()
{
int length = getMessage().size();
length -= (mHeader.getPadNibbleCount() * 4);
if(length > 4)
{
return getMessage().parseBCD4(0, (length / 4));
}
else
{
return "(insufficient data)";
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,7 +36,7 @@ public enum AnnouncementType
ADJACENT_SITE_INFORMATION(6, "NEIGHBOR SITE INFORMATION"),
GENERAL_SITE_INFORMATION(7, "SITE INFORMATION"),
RESERVED_8(8, "RESERVED 8"),
RESERVED_9(9, "RESERVED 8"),
RESERVED_9(9, "RESERVED 9"),
RESERVED_10(10, "RESERVED 10"),
RESERVED_11(11, "RESERVED 11"),
RESERVED_12(12, "RESERVED 12"),

View File

@@ -0,0 +1,103 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.type;
import com.google.common.base.Joiner;
import java.util.ArrayList;
import java.util.List;
/**
* Motorola Capacity Plus flavor of service options. Motorola uses the 2x reserved bits from the default implementation.
*/
public class CapacityPlusServiceOptions extends ServiceOptions
{
/**
* Constructs an instance
*
* @param value of the service options bitmap
*/
public CapacityPlusServiceOptions(int value)
{
super(value);
}
/**
* Reserved bit 1 is used to signal if the system is IP Site Connect or Conventional (true) or Capacity Plus (false).
* @return
*/
public boolean isCapacityPlus()
{
return !isReserved1();
}
/**
* Indicates if the transmission is interruptible.
* @return true if interruptible.
*/
public boolean isInterruptible()
{
return isReserved2();
}
@Override
public String toString()
{
List<String> flags = new ArrayList<>();
if(isEmergency())
{
flags.add("EMERGENCY");
}
if(isEncrypted())
{
flags.add("ENCRYPTED");
}
if(isBroadcastService())
{
flags.add("BROADCAST");
}
if(isOpenVoiceCallMode())
{
flags.add("OVCM");
}
if(isInterruptible())
{
flags.add("TXI CALL");
}
if(getPriority() > 0)
{
flags.add("PRIORITY-" + getPriority());
}
StringBuilder sb = new StringBuilder();
sb.append("SERVICE OPTIONS [");
if(!flags.isEmpty())
{
sb.append(Joiner.on(",").join(flags));
}
sb.append("]");
return sb.toString();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
package io.github.dsheirer.module.decode.dmr.message.type;
import com.google.common.base.Joiner;
import java.util.ArrayList;
import java.util.List;
@@ -31,6 +30,8 @@ public class ServiceOptions
{
private static final int EMERGENCY_MASK = 0x80;
private static final int ENCRYPTION_MASK = 0x40;
private static final int RESERVED_1 = 0x20;
private static final int RESERVED_2 = 0x10;
private static final int BROADCAST_SERVICE_MASK = 0x8;
private static final int OPEN_VOICE_CALL_MODE_MASK = 0x4;
private static final int PRIORITY_MASK = 0x3;
@@ -77,6 +78,24 @@ public class ServiceOptions
return isSet(OPEN_VOICE_CALL_MODE_MASK);
}
/**
* Indicates if Reserved 1 bit is set
* @return true if set
*/
public boolean isReserved1()
{
return isSet(RESERVED_1);
}
/**
* Indicates if Reserved 2 bit is set
* @return true if set
*/
public boolean isReserved2()
{
return isSet(RESERVED_2);
}
/**
* Priority of the communication
* @return priority 0-3, 0 = lowest/routine, 3 = highest
@@ -119,6 +138,16 @@ public class ServiceOptions
flags.add("OVCM");
}
if(isReserved1())
{
flags.add("RSVD1");
}
if(isReserved2())
{
flags.add("RSVD2");
}
if(getPriority() > 0)
{
flags.add("PRIORITY-" + getPriority());

Some files were not shown because too many files have changed in this diff Show More