Compare commits

...

11 Commits

Author SHA1 Message Date
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
393 changed files with 35421 additions and 582 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,3 +1,5 @@
import java.text.SimpleDateFormat
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
@@ -28,8 +30,14 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.5.1-beta1'
sourceCompatibility = '17'
version = '0.6.0-alpha2'
//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'
@@ -91,9 +99,10 @@ dependencies {
}
/**
* 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
}
@@ -101,8 +110,21 @@ 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
'--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'] //Allows Project Panama Foreign Function access to system library
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
if(os.isWindows()) {
//Replace the default Java system library path with an explicit location for the SDRplay API library (Windows)
applicationDefaultJvmArgs.add('-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64')
//On windows, we also need to export the windows swing look & feel module
applicationDefaultJvmArgs.add('--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED')
}
else {
//Replace the default Java system library path with an explicit location for the SDRplay API library (Linux/OS-X)
applicationDefaultJvmArgs.add("-Djava.library.path=/usr/local/lib")
}
}
jar {
@@ -111,7 +133,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']}"
)
@@ -212,7 +234,10 @@ runtime {
}
//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
modules = ['jdk.crypto.ec','jdk.incubator.vector','jdk.accessibility']
//Use auto-detected modules and 'add' any specified modules.
additive = true

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

@@ -19,13 +19,20 @@
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 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
@@ -1174,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,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

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

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

View File

@@ -0,0 +1,87 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.type;
/**
* Enumeration of talker alias data formatting options.
*
* ETSI 102 361-2 v2.4.1 paragraph 7.2.18
*/
public enum TalkerAliasDataFormat
{
BIT_7(0, 7, "ASCII-7"),
BIT_8(1, 8, "ASCII-8"),
UTF_8(2, 8, "UTF-8"),
UNICODE_UTF_16_BE(3, 16, "UNICODE-16");
private int mValue;
private int mBitsPerCharacter;
private String mLabel;
/**
* Constructs an instance
* @param value ordinal
*/
TalkerAliasDataFormat(int value, int bitsPerCharacter, String label)
{
mValue = value;
mBitsPerCharacter = bitsPerCharacter;
mLabel = label;
}
/**
* Ordinal value of the format
* @return ordinal value.
*/
public int getValue()
{
return mValue;
}
/**
* Number of bits per character for the format.
* @return bits per character
*/
public int getBitsPerCharacter()
{
return mBitsPerCharacter;
}
/**
* Lookup the format from the ordinal value.
* @param value to lookup in range 0-3
* @return format for the value.
*/
public static TalkerAliasDataFormat fromValue(int value)
{
if(value < 0 || value > 3)
{
throw new IllegalArgumentException("Invalid value - out of range (0-3): " + value);
}
return TalkerAliasDataFormat.values()[value];
}
@Override
public String toString()
{
return mLabel;
}
}

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
@@ -23,7 +23,6 @@ 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 java.util.Collections;
import java.util.List;
@@ -55,7 +54,7 @@ public class VoiceAMessage extends VoiceMessage
}
else
{
sb.append(" ").append(getSyncPattern());
sb.append("CC:- ").append(getSyncPattern());
}
return sb.toString();

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.module.decode.event;
@@ -50,6 +49,7 @@ public enum DecodeEventType
EMERGENCY("EMERGENCY"),
FUNCTION("Function"),
GPS("GPS"),
ICMP_PACKET("ICMP Packet"),
ID_ANI("ANI"),
ID_UNIQUE("Unique ID"),
IP_PACKET("IP Packet"),
@@ -63,9 +63,11 @@ public enum DecodeEventType
RESPONSE("Response"),
RESPONSE_PACKET("Response Packet"),
SDM("Short Data Message"),
SMS("SMS"),
STATION_ID("Station ID"),
STATUS("Status"),
UDP_PACKET("UDP/IP Packet"),
UNKNOWN_PACKET("Unknown Packet"),
UNKNOWN("Unknown");
private String mLabel;

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
@@ -43,8 +43,10 @@ import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
import io.github.dsheirer.module.decode.ip.IPacket;
import io.github.dsheirer.module.decode.ip.UnknownPacket;
import io.github.dsheirer.module.decode.ip.ars.ARSPacket;
import io.github.dsheirer.module.decode.ip.cellocator.MCGPPacket;
import io.github.dsheirer.module.decode.ip.icmp.ICMPPacket;
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.udp.UDPPacket;
@@ -87,7 +89,6 @@ import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUn
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrant;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.ambtc.osp.AMBTCUnitToUnitVoiceServiceChannelGrantUpdate;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.PacketMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.packet.sndcp.SNDCPPacketMessage;
import io.github.dsheirer.module.decode.p25.phase1.message.pdu.umbtc.isp.UMBTCTelephoneInterconnectRequestExplicitDialing;
import io.github.dsheirer.module.decode.p25.phase1.message.tdu.TDULinkControlMessage;
@@ -139,12 +140,11 @@ import io.github.dsheirer.module.decode.p25.reference.ServiceOptions;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.util.PacketUtil;
import java.util.List;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Decoder state for an APCO25 channel. Maintains the call/data/idle state of the channel and produces events by
* monitoring the decoded message stream.
@@ -877,12 +877,12 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
if(message instanceof SNDCPPacketMessage)
{
SNDCPPacketMessage sndcp = (SNDCPPacketMessage)message;
SNDCPPacketMessage sndcp = (SNDCPPacketMessage) message;
getIdentifierCollection().update(sndcp.getIdentifiers());
}
else if(message instanceof PacketMessage)
{
PacketMessage packetMessage = (PacketMessage)message;
PacketMessage packetMessage = (PacketMessage) message;
getIdentifierCollection().remove(IdentifierClass.USER);
getIdentifierCollection().update(packetMessage.getIdentifiers());
@@ -890,41 +890,41 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
if(packet instanceof IPV4Packet)
{
IPV4Packet ipv4 = (IPV4Packet)packet;
IPV4Packet ipv4 = (IPV4Packet) packet;
IPacket ipPayload = ipv4.getPayload();
if(ipPayload instanceof UDPPacket)
{
UDPPacket udpPacket = (UDPPacket)ipPayload;
UDPPacket udpPacket = (UDPPacket) ipPayload;
IPacket udpPayload = udpPacket.getPayload();
if(udpPayload instanceof ARSPacket)
{
ARSPacket arsPacket = (ARSPacket)udpPayload;
ARSPacket arsPacket = (ARSPacket) udpPayload;
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
for(Identifier identifier: packet.getIdentifiers())
for(Identifier identifier : packet.getIdentifiers())
{
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE.toString())
.identifiers(ic)
.details(arsPacket.toString() + " " + ipv4.toString())
.build();
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE.toString())
.identifiers(ic)
.details(arsPacket.toString() + " " + ipv4.toString())
.build();
broadcast(packetEvent);
}
else if(udpPayload instanceof MCGPPacket)
{
MCGPPacket mcgpPacket = (MCGPPacket)udpPayload;
MCGPPacket mcgpPacket = (MCGPPacket) udpPayload;
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
for(Identifier identifier: packet.getIdentifiers())
for(Identifier identifier : packet.getIdentifiers())
{
ic.update(identifier);
}
@@ -954,7 +954,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
GeoPosition geoPosition = PacketUtil.extractGeoPosition(lrrpPacket);
if (geoPosition != null)
if(geoPosition != null)
{
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(message.getTimestamp())
.channel(getCurrentChannel())
@@ -970,21 +970,72 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
else
{
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
for(Identifier identifier: packet.getIdentifiers())
for(Identifier identifier : packet.getIdentifiers())
{
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.UDP_PACKET.toString())
.identifiers(ic)
.details(ipv4.toString())
.build();
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.UDP_PACKET.toString())
.identifiers(ic)
.details(ipv4.toString())
.build();
broadcast(packetEvent);
}
}
else if(ipPayload instanceof ICMPPacket)
{
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
for(Identifier identifier : packet.getIdentifiers())
{
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.ICMP_PACKET.toString())
.identifiers(ic)
.details(ipv4.toString())
.build();
broadcast(packetEvent);
}
else
{
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
for(Identifier identifier : packet.getIdentifiers())
{
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.IP_PACKET.toString())
.identifiers(ic)
.details(ipv4.toString())
.build();
broadcast(packetEvent);
}
}
else if(packet instanceof UnknownPacket)
{
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
for(Identifier identifier : packet.getIdentifiers())
{
ic.update(identifier);
}
DecodeEvent packetEvent = P25DecodeEvent.builder(message.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.UNKNOWN_PACKET.toString())
.identifiers(ic)
.details(packet.toString())
.build();
broadcast(packetEvent);
}
}
}
@@ -992,18 +1043,14 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
/**
* Sub-Network Dependent Convergence Protocol (SNDCP)
*
* @param message
* @param message to process
*/
private void processSNDCP(P25Message message)
{
broadcast(new DecoderStateEvent(this, Event.DECODE, State.DATA));
if(message.isValid() && message instanceof SNDCPPacketMessage)
if(message.isValid() && message instanceof SNDCPPacketMessage sndcpPacket)
{
SNDCPPacketMessage sndcpPacket = (SNDCPPacketMessage)message;
SNDCPMessage sndcpMessage = sndcpPacket.getSNDCPMessage();
MutableIdentifierCollection ic = getMutableIdentifierCollection(sndcpPacket.getIdentifiers());
switch(sndcpPacket.getSNDCPPacketHeader().getPDUType())

View File

@@ -19,14 +19,14 @@
package io.github.dsheirer.preference.source;
import io.github.dsheirer.gui.preference.tuner.RspDuoSelectionMode;
import io.github.dsheirer.preference.Preference;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.sample.Listener;
import java.util.prefs.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.prefs.Preferences;
/**
* Tuner preferences
*/
@@ -35,8 +35,10 @@ public class TunerPreference extends Preference
private final static Logger mLog = LoggerFactory.getLogger(TunerPreference.class);
private Preferences mPreferences = Preferences.userNodeForPackage(TunerPreference.class);
private static final String PREFERENCE_KEY_CHANNELIZER_TYPE = "channelizer.type";
private static final String PREFERENCE_KEY_RSP_DUO_TUNER_MODE = "rsp.duo.tuner.mode";
private ChannelizerType mChannelizerType;
private RspDuoSelectionMode mRspDuoSelectionMode;
/**
* Constructs a tuner preference with the update listener
@@ -94,4 +96,30 @@ public class TunerPreference extends Preference
mPreferences.put(PREFERENCE_KEY_CHANNELIZER_TYPE, mChannelizerType.name());
notifyPreferenceUpdated();
}
/**
* RSPduo tuner select mode.
* @return mode or a default value of DUAL
*/
public RspDuoSelectionMode getRspDuoTunerMode()
{
if(mRspDuoSelectionMode == null)
{
String mode = mPreferences.get(PREFERENCE_KEY_RSP_DUO_TUNER_MODE, RspDuoSelectionMode.DUAL.name());
mRspDuoSelectionMode = RspDuoSelectionMode.fromValue(mode);
}
return mRspDuoSelectionMode;
}
/**
* Sets the RSPduo tuner select mode
* @param mode to use
*/
public void setRspDuoTunerMode(RspDuoSelectionMode mode)
{
mRspDuoSelectionMode = mode;
mPreferences.put(PREFERENCE_KEY_RSP_DUO_TUNER_MODE, mRspDuoSelectionMode.name());
notifyPreferenceUpdated();
}
}

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
@@ -18,9 +18,6 @@
*/
package io.github.dsheirer.record.binary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
@@ -29,6 +26,8 @@ import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BinaryReader implements Iterator<ByteBuffer>, AutoCloseable
{
@@ -94,7 +93,7 @@ public class BinaryReader implements Iterator<ByteBuffer>, AutoCloseable
byte[] readBytes = new byte[mBufferSize];
int bytesRead = mInputStream.read(readBytes);
if(bytesRead < readBytes.length)
if(bytesRead < readBytes.length && bytesRead > 0)
{
readBytes = Arrays.copyOf(readBytes, bytesRead);
}

View File

@@ -18,12 +18,13 @@
*/
package io.github.dsheirer.sample;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Broadcasts an item to multiple listeners
@@ -100,6 +101,10 @@ public class Broadcaster<T> implements Listener<T>
mListeners.add(listener);
}
}
catch(Exception e)
{
mLog.error("Unexpected error while adding listener", e);
}
finally
{
mLock.unlock();
@@ -120,6 +125,10 @@ public class Broadcaster<T> implements Listener<T>
{
mListeners.remove(listener);
}
catch(Exception e)
{
mLog.error("Unexpected error while removing listener", e);
}
finally
{
mLock.unlock();
@@ -151,6 +160,10 @@ public class Broadcaster<T> implements Listener<T>
listener.receive(t);
}
}
catch(Exception e)
{
mLog.error("Unexpected error while broadcasting to listeners", e);
}
finally
{
mLock.unlock();

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.source.tuner;
@@ -29,4 +26,9 @@ public interface ITunerErrorListener
* @param errorMessage to set
*/
void setErrorMessage(String errorMessage);
/**
* Indicates that the tuner has been removed from the system.
*/
void tunerRemoved();
}

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
@@ -34,4 +34,10 @@ public class LoggingTunerErrorListener implements ITunerErrorListener
{
mLog.error("Tuner Error: " + errorMessage);
}
@Override
public void tunerRemoved()
{
mLog.warn("Tuner removal detected");
}
}

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
@@ -149,6 +149,20 @@ public abstract class Tuner implements ISourceEventProcessor, ITunerErrorListene
}
}
/**
* Process tuner removal error from tuner controller and propagate to an external listener.
*/
@Override
public void tunerRemoved()
{
stop();
if(mTunerErrorListener != null)
{
mTunerErrorListener.tunerRemoved();
}
}
/**
* Sets the channel source manager
* @param manager to use

View File

@@ -30,6 +30,7 @@ public enum TunerClass
FUNCUBE_DONGLE_PRO_PLUS("Funcube Dongle Pro+" ),
HACKRF("HackRF" ),
RTL2832("RTL-2832"),
RSP("RSP"),
TEST_TUNER("Test"),
RECORDING_TUNER("Recording"),
UNKNOWN("Unknown" );

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
@@ -46,7 +46,11 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
INativeBufferProvider, Listener<INativeBuffer>, ITunerErrorListener
{
private final static Logger mLog = LoggerFactory.getLogger(TunerController.class);
//Protects access to the native buffer broadcaster for adding, removing or checking for listener count.
protected ReentrantLock mBufferListenerLock = new ReentrantLock();
protected Broadcaster<INativeBuffer> mNativeBufferBroadcaster = new Broadcaster();
protected FrequencyController mFrequencyController;
private int mMiddleUnusableHalfBandwidth;
private int mMeasuredFrequencyError;
@@ -129,6 +133,19 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
}
}
/**
* Indicates that the tuner has been removed from the system and requests the listener to process the cleanup.
*/
@Override
public void tunerRemoved()
{
//Request parent tuner process the shutdown and cleanup
if(mTunerErrorListener != null)
{
mTunerErrorListener.tunerRemoved();
}
}
/**
* Number of samples contained in each complex buffer provided by this tuner.
*
@@ -572,7 +589,16 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
@Override
public void addBufferListener(Listener<INativeBuffer> listener)
{
mNativeBufferBroadcaster.addListener(listener);
mBufferListenerLock.lock();
try
{
mNativeBufferBroadcaster.addListener(listener);
}
finally
{
mBufferListenerLock.unlock();
}
}
/**
@@ -581,7 +607,16 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
@Override
public void removeBufferListener(Listener<INativeBuffer> listener)
{
mNativeBufferBroadcaster.removeListener(listener);
mBufferListenerLock.lock();
try
{
mNativeBufferBroadcaster.removeListener(listener);
}
finally
{
mBufferListenerLock.unlock();
}
}
/**
@@ -590,7 +625,20 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
@Override
public boolean hasBufferListeners()
{
return mNativeBufferBroadcaster.hasListeners();
boolean hasListeners;
mBufferListenerLock.lock();
try
{
hasListeners = mNativeBufferBroadcaster.hasListeners();
}
finally
{
mBufferListenerLock.unlock();
}
return hasListeners;
}
/**
@@ -598,6 +646,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
*/
protected void broadcast(INativeBuffer complexSamples)
{
//Note: unprotected access to the broadcaster ... the broadcaster uses thread-save internal list
mNativeBufferBroadcaster.broadcast(complexSamples);
}

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
@@ -54,6 +54,24 @@ public class TunerEvent
return mTuner != null;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Tuner Event [").append(getEvent().name()).append("] for tuner [");
if(hasTuner())
{
sb.append(getTuner());
}
else
{
sb.append("No Tuner");
}
sb.append("]");
return sb.toString();
}
/**
* Event type
*/
@@ -75,6 +93,7 @@ public class TunerEvent
NOTIFICATION_SHUTTING_DOWN,
REQUEST_CLEAR_MAIN_SPECTRAL_DISPLAY,
REQUEST_ENABLE_RSP_SLAVE_DEVICE,
REQUEST_MAIN_SPECTRAL_DISPLAY,
REQUEST_NEW_SPECTRAL_DISPLAY;
}

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.source.tuner;
import io.github.dsheirer.gui.preference.tuner.RspDuoSelectionMode;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.source.ChannelizerType;
import io.github.dsheirer.source.SourceException;
@@ -53,7 +54,60 @@ import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerEditor;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TEmbeddedTuner;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerEditor;
import io.github.dsheirer.source.tuner.sdrplay.DiscoveredRspTuner;
import io.github.dsheirer.source.tuner.sdrplay.RspTuner;
import io.github.dsheirer.source.tuner.sdrplay.api.DeviceSelectionMode;
import io.github.dsheirer.source.tuner.sdrplay.api.SDRPlayException;
import io.github.dsheirer.source.tuner.sdrplay.api.SDRplay;
import io.github.dsheirer.source.tuner.sdrplay.api.device.Device;
import io.github.dsheirer.source.tuner.sdrplay.api.device.DeviceInfo;
import io.github.dsheirer.source.tuner.sdrplay.api.device.Rsp1Device;
import io.github.dsheirer.source.tuner.sdrplay.api.device.Rsp1aDevice;
import io.github.dsheirer.source.tuner.sdrplay.api.device.Rsp2Device;
import io.github.dsheirer.source.tuner.sdrplay.api.device.RspDuoDevice;
import io.github.dsheirer.source.tuner.sdrplay.api.device.RspDxDevice;
import io.github.dsheirer.source.tuner.sdrplay.rsp1.ControlRsp1;
import io.github.dsheirer.source.tuner.sdrplay.rsp1.DiscoveredRsp1Tuner;
import io.github.dsheirer.source.tuner.sdrplay.rsp1.IControlRsp1;
import io.github.dsheirer.source.tuner.sdrplay.rsp1.Rsp1TunerConfiguration;
import io.github.dsheirer.source.tuner.sdrplay.rsp1.Rsp1TunerController;
import io.github.dsheirer.source.tuner.sdrplay.rsp1.Rsp1TunerEditor;
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.ControlRsp1a;
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.DiscoveredRsp1aTuner;
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.IControlRsp1a;
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.Rsp1aTunerConfiguration;
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.Rsp1aTunerController;
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.Rsp1aTunerEditor;
import io.github.dsheirer.source.tuner.sdrplay.rsp2.ControlRsp2;
import io.github.dsheirer.source.tuner.sdrplay.rsp2.DiscoveredRsp2Tuner;
import io.github.dsheirer.source.tuner.sdrplay.rsp2.IControlRsp2;
import io.github.dsheirer.source.tuner.sdrplay.rsp2.Rsp2TunerConfiguration;
import io.github.dsheirer.source.tuner.sdrplay.rsp2.Rsp2TunerController;
import io.github.dsheirer.source.tuner.sdrplay.rsp2.Rsp2TunerEditor;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.ControlRspDuoTuner1Master;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.ControlRspDuoTuner1Single;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.ControlRspDuoTuner2Single;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.ControlRspDuoTuner2Slave;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.DiscoveredRspDuoTuner1;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.DiscoveredRspDuoTuner2;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.IControlRspDuoTuner1;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.IControlRspDuoTuner2;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.MasterSlaveBridge;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.RspDuoTuner1Configuration;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.RspDuoTuner1Controller;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.RspDuoTuner1Editor;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.RspDuoTuner2Configuration;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.RspDuoTuner2Controller;
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.RspDuoTuner2Editor;
import io.github.dsheirer.source.tuner.sdrplay.rspDx.ControlRspDx;
import io.github.dsheirer.source.tuner.sdrplay.rspDx.DiscoveredRspDxTuner;
import io.github.dsheirer.source.tuner.sdrplay.rspDx.IControlRspDx;
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerConfiguration;
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerController;
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerEditor;
import io.github.dsheirer.source.tuner.ui.TunerEditor;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,6 +120,182 @@ public class TunerFactory
{
private static final Logger mLog = LoggerFactory.getLogger(TunerFactory.class);
/**
* Creates one or two (e.g. RSPduo) Discovered RSP tuner instances for the RSP device.
* @param deviceInfo describing the RSP device
* @param channelizerType to use with the device
* @param selectionMode to specify how to configure an RSPduo dual-tuner device.
* @return zero or more discovered RSP tuners.
*/
public static List<DiscoveredRspTuner> getRspTuners(DeviceInfo deviceInfo, ChannelizerType channelizerType, RspDuoSelectionMode selectionMode)
{
List<DiscoveredRspTuner> tuners = new ArrayList<>();
switch(deviceInfo.getDeviceType())
{
case RSP1:
tuners.add(new DiscoveredRsp1Tuner(deviceInfo, channelizerType));
break;
case RSP1A:
tuners.add(new DiscoveredRsp1aTuner(deviceInfo, channelizerType));
break;
case RSP2:
tuners.add(new DiscoveredRsp2Tuner(deviceInfo, channelizerType));
break;
case RSPdx:
tuners.add(new DiscoveredRspDxTuner(deviceInfo, channelizerType));
break;
case RSPduo:
switch(selectionMode)
{
case SINGLE_1:
deviceInfo.setDeviceSelectionMode(DeviceSelectionMode.SINGLE_TUNER_1);
tuners.add(new DiscoveredRspDuoTuner1(deviceInfo, channelizerType));
break;
case SINGLE_2:
deviceInfo.setDeviceSelectionMode(DeviceSelectionMode.SINGLE_TUNER_2);
tuners.add(new DiscoveredRspDuoTuner2(deviceInfo, channelizerType));
break;
case DUAL:
MasterSlaveBridge bridge = new MasterSlaveBridge();
deviceInfo.setDeviceSelectionMode(DeviceSelectionMode.MASTER_TUNER_1);
tuners.add(new DiscoveredRspDuoTuner1(deviceInfo, channelizerType, bridge));
DeviceInfo deviceInfoTuner2 = deviceInfo.clone();
deviceInfoTuner2.setDeviceSelectionMode(DeviceSelectionMode.SLAVE_TUNER_2);
tuners.add(new DiscoveredRspDuoTuner2(deviceInfoTuner2, channelizerType, bridge));
break;
}
break;
}
return tuners;
}
/**
* Locates the matching RSP device described by the device info object and creates an RSP tuner instance.
* @param deviceInfo to match
* @param channelizerType for the tuner
* @param tunerErrorListener to monitor the tuner's status
* @return constructed RSP tuner instance
* @throws SDRPlayException if the device is not available or is not supported
*/
public static RspTuner getRspTuner(DeviceInfo deviceInfo, ChannelizerType channelizerType,
ITunerErrorListener tunerErrorListener) throws SDRPlayException
{
//API instance is retained across the lifecycle of the constructed device, so we only close it if we don't get
//a device from it.
SDRplay api = new SDRplay();
if(api.isAvailable())
{
Device device = api.getDevice(deviceInfo);
switch(device.getDeviceType())
{
case RSP1:
if(device instanceof Rsp1Device rsp1Device)
{
IControlRsp1 controlRsp1 = new ControlRsp1(rsp1Device);
Rsp1TunerController rsp1TunerController = new Rsp1TunerController(controlRsp1, tunerErrorListener);
return new RspTuner(rsp1TunerController, tunerErrorListener, channelizerType);
}
break;
case RSP1A:
if(device instanceof Rsp1aDevice rsp1aDevice)
{
IControlRsp1a controlRsp1a = new ControlRsp1a(rsp1aDevice);
Rsp1aTunerController rsp1aTunerController = new Rsp1aTunerController(controlRsp1a, tunerErrorListener);
return new RspTuner(rsp1aTunerController, tunerErrorListener, channelizerType);
}
break;
case RSP2:
if(device instanceof Rsp2Device rsp2Device)
{
IControlRsp2 controlRsp2 = new ControlRsp2(rsp2Device);
Rsp2TunerController rsp2TunerController = new Rsp2TunerController(controlRsp2, tunerErrorListener);
return new RspTuner(rsp2TunerController, tunerErrorListener, channelizerType);
}
break;
case RSPdx:
if(device instanceof RspDxDevice rspDxDevice)
{
IControlRspDx controlRspDx = new ControlRspDx(rspDxDevice);
RspDxTunerController rspDxTunerController = new RspDxTunerController(controlRspDx, tunerErrorListener);
return new RspTuner(rspDxTunerController, tunerErrorListener, channelizerType);
}
break;
case RSPduo:
if(device instanceof RspDuoDevice rspDuoDevice)
{
switch(deviceInfo.getDeviceSelectionMode())
{
case SINGLE_TUNER_1:
IControlRspDuoTuner1 controlRspDuoTuner1 = new ControlRspDuoTuner1Single(rspDuoDevice);
RspDuoTuner1Controller rspDuoTuner1Controller = new RspDuoTuner1Controller(controlRspDuoTuner1, tunerErrorListener);
return new RspTuner(rspDuoTuner1Controller, tunerErrorListener, channelizerType);
case SINGLE_TUNER_2:
IControlRspDuoTuner2 controlRspDuoTuner2 = new ControlRspDuoTuner2Single(rspDuoDevice);
RspDuoTuner2Controller rspDuoTuner2Controller = new RspDuoTuner2Controller(controlRspDuoTuner2, tunerErrorListener);
return new RspTuner(rspDuoTuner2Controller, tunerErrorListener, channelizerType);
default:
throw new SDRPlayException("This method only supports RSPduo single tuner configurations");
}
}
break;
case default:
mLog.warn("Unrecognized SDRplay RSP Device Type: " + device.getDeviceType() + " SER#: " + device.getSerialNumber());
break;
}
}
throw new SDRPlayException("Unable to obtain RSP tuner");
}
/**
* Locates the matching RSP device described by the device info object and creates an RSP tuner instance.
* @param deviceInfo to match
* @param channelizerType for the tuner
* @param tunerErrorListener to monitor the tuner's status
* @param bridge to link master/slave instances together
* @return constructed RSP tuner instance
* @throws SDRPlayException if the device is not available or is not supported
*/
public static RspTuner getRspDuoTuner(DeviceInfo deviceInfo, ChannelizerType channelizerType,
ITunerErrorListener tunerErrorListener, MasterSlaveBridge bridge) throws SDRPlayException
{
SDRplay api = new SDRplay();
if(api.isAvailable())
{
Device device = api.getDevice(deviceInfo);
switch(device.getDeviceType())
{
case RSPduo:
if(device instanceof RspDuoDevice rspDuoDevice)
{
switch(deviceInfo.getDeviceSelectionMode())
{
case MASTER_TUNER_1:
IControlRspDuoTuner1 controlRspDuoTuner1 = new ControlRspDuoTuner1Master(rspDuoDevice, bridge);
RspDuoTuner1Controller rspDuoTuner1Controller = new RspDuoTuner1Controller(controlRspDuoTuner1, tunerErrorListener);
return new RspTuner(rspDuoTuner1Controller, tunerErrorListener, channelizerType);
case SLAVE_TUNER_2:
IControlRspDuoTuner2 controlRspDuoTuner2 = new ControlRspDuoTuner2Slave(rspDuoDevice, bridge);
RspDuoTuner2Controller rspDuoTuner2Controller = new RspDuoTuner2Controller(controlRspDuoTuner2, tunerErrorListener);
return new RspTuner(rspDuoTuner2Controller, tunerErrorListener, channelizerType);
default:
throw new SDRPlayException("This method only supports RSPduo single tuner configurations");
}
}
break;
}
}
throw new SDRPlayException("Unable to obtain RSPduo tuner");
}
/**
* Create a USB tuner
* @param tunerClass to instantiate
@@ -148,6 +378,18 @@ public class TunerFactory
return new R820TTunerConfiguration(uniqueID);
case RECORDING:
return RecordingTunerConfiguration.create();
case RSP_1:
return new Rsp1TunerConfiguration(uniqueID);
case RSP_1A:
return new Rsp1aTunerConfiguration(uniqueID);
case RSP_2:
return new Rsp2TunerConfiguration(uniqueID);
case RSP_DUO_1:
return new RspDuoTuner1Configuration(uniqueID);
case RSP_DUO_2:
return new RspDuoTuner2Configuration(uniqueID);
case RSP_DX:
return new RspDxTunerConfiguration(uniqueID);
default:
throw new IllegalArgumentException("Unrecognized tuner type [" + type.name() + "]");
}
@@ -169,6 +411,42 @@ public class TunerFactory
return new FCD2TunerEditor(userPreferences, tunerManager, discoveredTuner);
case HACKRF:
return new HackRFTunerEditor(userPreferences, tunerManager, discoveredTuner);
case RSP:
if(discoveredTuner instanceof DiscoveredRspTuner discoveredRspTuner)
{
switch(discoveredRspTuner.getDeviceType())
{
case RSP1:
return new Rsp1TunerEditor(userPreferences, tunerManager, discoveredRspTuner);
case RSP1A:
return new Rsp1aTunerEditor(userPreferences, tunerManager, discoveredRspTuner);
case RSP2:
return new Rsp2TunerEditor(userPreferences, tunerManager, discoveredRspTuner);
case RSPdx:
return new RspDxTunerEditor(userPreferences, tunerManager, discoveredRspTuner);
case RSPduo:
if(discoveredRspTuner instanceof DiscoveredRspDuoTuner1 duoTuner1)
{
return new RspDuoTuner1Editor(userPreferences, tunerManager, duoTuner1);
}
else if(discoveredRspTuner instanceof DiscoveredRspDuoTuner2 duoTuner2)
{
return new RspDuoTuner2Editor(userPreferences, tunerManager, duoTuner2);
}
else
{
throw new IllegalArgumentException("Unrecognized RSPduo device type:" +
discoveredRspTuner.getClass());
}
case UNKNOWN:
throw new IllegalArgumentException("Unrecognized RSP device type: " +
discoveredRspTuner.getDeviceType());
}
}
throw new IllegalArgumentException("Unrecognized discovered RSP tuner class: " +
discoveredTuner.getClass());
case RECORDING_TUNER:
return new RecordingTunerEditor(userPreferences, tunerManager, discoveredTuner);
case RTL2832:
if(discoveredTuner.hasTuner())
{
@@ -181,8 +459,6 @@ public class TunerFactory
}
}
return new RTL2832UnknownTunerEditor(userPreferences, tunerManager, discoveredTuner);
case RECORDING_TUNER:
return new RecordingTunerEditor(userPreferences, tunerManager, discoveredTuner);
case TEST_TUNER:
case UNKNOWN:
default:

View File

@@ -37,6 +37,13 @@ public enum TunerType
HACKRF_RAD1O("RAD1O"),
RAFAELMICRO_R820T("R820T"),
RAFAELMICRO_R828D("R828D"),
RSP_1("RSP1"),
RSP_1A("RSP1A"),
RSP_2("RSP2"),
RSP_DUO_1("RSPduo Tuner 1"),
RSP_DUO_2("RSPduo Tuner 2"),
RSP_DX("RSPdx"),
TEST("Test"),
RECORDING("Recording"),
UNKNOWN("Unknown");

View File

@@ -31,6 +31,7 @@ import io.github.dsheirer.source.tuner.hackrf.HackRFTunerConfiguration;
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.e4k.E4KTunerConfiguration;
import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
/**
* Abstract class to hold a configuration for a specific type of tuner
@@ -44,6 +45,7 @@ import io.github.dsheirer.source.tuner.rtl.r820t.R820TTunerConfiguration;
@JsonSubTypes.Type(value = HackRFTunerConfiguration.class, name = "hackRFTunerConfiguration"),
@JsonSubTypes.Type(value = RecordingTunerConfiguration.class, name = "recordingTunerConfiguration"),
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
@JsonSubTypes.Type(value = RspTunerConfiguration.class, name = "rspTunerConfiguration"),
})
@JacksonXmlRootElement(localName = "tuner_configuration")
public abstract class TunerConfiguration

View File

@@ -147,6 +147,12 @@ public class TunerConfigurationManager implements IDiscoveredTunerStatusListener
}
}
/**
* Monitors discovered tuner enabled status and applies configurations or updates disable state of tuners.
* @param discoveredTuner that has a status change.
* @param previous tuner status
* @param current tuner status
*/
@Override
public void tunerStatusUpdated(DiscoveredTuner discoveredTuner, TunerStatus previous, TunerStatus current)
{
@@ -157,6 +163,23 @@ public class TunerConfigurationManager implements IDiscoveredTunerStatusListener
else if(current == TunerStatus.ENABLED)
{
removeDisabledTuner(discoveredTuner);
if(discoveredTuner.hasTuner())
{
TunerType tunerType = discoveredTuner.getTuner().getTunerType();
if(tunerType != TunerType.RECORDING)
{
TunerConfiguration tunerConfiguration = getTunerConfiguration(tunerType, discoveredTuner.getId());
if(tunerConfiguration != null)
{
discoveredTuner.setTunerConfiguration(tunerConfiguration);
saveConfigurations();
}
}
}
}
}

View File

@@ -219,7 +219,10 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
*/
public void addTunerStatusListener(IDiscoveredTunerStatusListener listener)
{
mListeners.add(listener);
if(!mListeners.contains(listener))
{
mListeners.add(listener);
}
}
/**
@@ -257,6 +260,12 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
setTunerStatus(TunerStatus.ERROR);
}
@Override
public void tunerRemoved()
{
setTunerStatus(TunerStatus.REMOVED);
}
/**
* Indicates if this tuner has an error message.
*/

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