mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 10:07:26 -05:00
Compare commits
60 Commits
v0.6.0-alp
...
v0.6.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02363c3113 | ||
|
|
cfaac0fcbc | ||
|
|
bee0405e00 | ||
|
|
50bdf0ee81 | ||
|
|
a27ddbe0d0 | ||
|
|
0d652b9153 | ||
|
|
9468fd189b | ||
|
|
215737ad9b | ||
|
|
123c25b142 | ||
|
|
af56198f0e | ||
|
|
3c73fdfd6e | ||
|
|
b391ebbb5f | ||
|
|
bad5a1f9f0 | ||
|
|
a7470b463f | ||
|
|
d6c706cb35 | ||
|
|
a0d6950c75 | ||
|
|
2495200b36 | ||
|
|
bfe7ae0b1b | ||
|
|
4912abbf07 | ||
|
|
2f2998d02e | ||
|
|
c96349fa1c | ||
|
|
f640576cc3 | ||
|
|
77d725a488 | ||
|
|
2c0305f928 | ||
|
|
21fd61d507 | ||
|
|
8fb56a5342 | ||
|
|
5d7b73f876 | ||
|
|
85b2a38058 | ||
|
|
c78517ee75 | ||
|
|
ae116d3dfe | ||
|
|
93252f9a40 | ||
|
|
3c03a4db2e | ||
|
|
d0fcd617aa | ||
|
|
6ca0decbd6 | ||
|
|
ddd9ca933e | ||
|
|
b2cc29569e | ||
|
|
411735772b | ||
|
|
2ffb2963af | ||
|
|
227c88c8a2 | ||
|
|
cbe9c79498 | ||
|
|
1b8c100b43 | ||
|
|
30c5fd64f2 | ||
|
|
3a6b486340 | ||
|
|
ded7335dbf | ||
|
|
005a4f8119 | ||
|
|
1ac49bfa7f | ||
|
|
c302c4d121 | ||
|
|
b0a524cf62 | ||
|
|
d246ffff30 | ||
|
|
adfc8b2d6f | ||
|
|
ad2ab08466 | ||
|
|
f3369410b2 | ||
|
|
bac38a5bd8 | ||
|
|
e5f5df74ad | ||
|
|
b3837f581a | ||
|
|
b2ab6f58e9 | ||
|
|
e04b59ba15 | ||
|
|
366eacd234 | ||
|
|
f792876c0a | ||
|
|
19f7a5171b |
8
.github/workflows/gradle.yml
vendored
8
.github/workflows/gradle.yml
vendored
@@ -22,13 +22,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 19
|
- name: Set up JDK 20
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '19'
|
java-version: '20'
|
||||||
distribution: 'liberica'
|
distribution: 'liberica'
|
||||||
java-package: 'jdk+fx'
|
java-package: 'jdk+fx'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: clean build
|
arguments: clean build
|
||||||
|
|||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_19_PREVIEW" project-jdk-name="19" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
230
artifacts/sdrplay-api-headers/v3_08/sdrplay_api.h
Normal file
230
artifacts/sdrplay-api-headers/v3_08/sdrplay_api.h
Normal 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)
|
||||||
|
//#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.08)
|
||||||
|
|
||||||
|
// 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_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Open(void);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Close(void);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
|
||||||
|
//
|
||||||
|
// // Device API function definitions
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER 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
|
||||||
78
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_callback.h
Normal file
78
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_callback.h
Normal 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
|
||||||
55
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_control.h
Normal 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
|
||||||
51
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_dev.h
Normal 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
|
||||||
22
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp1a.h
Normal 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
|
||||||
35
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp2.h
Normal 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
|
||||||
40
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDuo.h
Normal file
40
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDuo.h
Normal 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
|
||||||
45
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDx.h
Normal file
45
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDx.h
Normal 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
|
||||||
21
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rx_channel.h
Normal 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
|
||||||
93
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_tuner.h
Normal 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
|
||||||
19
build.gradle
19
build.gradle
@@ -53,12 +53,13 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.6.0-alpha6'
|
//Note: this should match the GitLab release tag.
|
||||||
|
version = '0.6.0-beta-3'
|
||||||
|
|
||||||
//Java 19 is required for this version of the Project Panama preview/incubator feature
|
//Java 20 is required for this version of the Project Panama preview/incubator feature
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(19)
|
languageVersion = JavaLanguageVersion.of(20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,16 +198,16 @@ String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
|||||||
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
||||||
*/
|
*/
|
||||||
String jdk_base = '/home/denny/java_jdks/'
|
String jdk_base = '/home/denny/java_jdks/'
|
||||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
|
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-20.0.1-full'
|
||||||
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
|
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-20.0.1-full'
|
||||||
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-20.0.1-full.jdk'
|
||||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
|
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-20.0.1-full.jdk'
|
||||||
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
|
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-20.0.1-full'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download URLs to download the JDK as part of the gradle build packaging process
|
* Download URLs to download the JDK as part of the gradle build packaging process
|
||||||
*/
|
*/
|
||||||
def jdk_download_base = "https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-"
|
def jdk_download_base = "https://download.bell-sw.com/java/20.0.1+10/bellsoft-jdk20.0.1+10-"
|
||||||
def jdk_download_suffix = "-full.tar.gz"
|
def jdk_download_suffix = "-full.tar.gz"
|
||||||
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
|
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
|
||||||
def jdk_download_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix
|
def jdk_download_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix
|
||||||
|
|||||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#
|
#
|
||||||
# ******************************************************************************
|
# ******************************************************************************
|
||||||
# Copyright (C) 2014-2022 Dennis Sheirer
|
# Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,22 +19,15 @@
|
|||||||
package io.github.dsheirer.alias;
|
package io.github.dsheirer.alias;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.action.AliasAction;
|
import io.github.dsheirer.alias.action.AliasAction;
|
||||||
import io.github.dsheirer.alias.action.AliasActionType;
|
|
||||||
import io.github.dsheirer.alias.action.beep.BeepAction;
|
import io.github.dsheirer.alias.action.beep.BeepAction;
|
||||||
import io.github.dsheirer.alias.action.clip.ClipAction;
|
import io.github.dsheirer.alias.action.clip.ClipAction;
|
||||||
import io.github.dsheirer.alias.action.script.ScriptAction;
|
import io.github.dsheirer.alias.action.script.ScriptAction;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.AliasIDType;
|
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
||||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
|
||||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
@@ -45,11 +38,10 @@ import io.github.dsheirer.alias.id.status.UserStatusID;
|
|||||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class AliasFactory
|
public class AliasFactory
|
||||||
{
|
{
|
||||||
@@ -64,6 +56,11 @@ public class AliasFactory
|
|||||||
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
||||||
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
||||||
return copyBroadcast;
|
return copyBroadcast;
|
||||||
|
case DCS:
|
||||||
|
Dcs originalDcs = (Dcs)id;
|
||||||
|
Dcs copyDcs = new Dcs();
|
||||||
|
copyDcs.setDCSCode(originalDcs.getDCSCode());
|
||||||
|
return copyDcs;
|
||||||
case ESN:
|
case ESN:
|
||||||
Esn originalESN = (Esn)id;
|
Esn originalESN = (Esn)id;
|
||||||
Esn copyESN = new Esn();
|
Esn copyESN = new Esn();
|
||||||
@@ -234,65 +231,4 @@ public class AliasFactory
|
|||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AliasID getAliasID(AliasIDType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case BROADCAST_CHANNEL:
|
|
||||||
return new BroadcastChannel();
|
|
||||||
case ESN:
|
|
||||||
return new Esn();
|
|
||||||
case FLEETSYNC:
|
|
||||||
return new FleetsyncID();
|
|
||||||
case LTR_NET_UID:
|
|
||||||
return new UniqueID();
|
|
||||||
case LOJACK:
|
|
||||||
return new LoJackFunctionAndID();
|
|
||||||
case MDC1200:
|
|
||||||
return new MDC1200ID();
|
|
||||||
case MIN:
|
|
||||||
return new Min();
|
|
||||||
case MPT1327:
|
|
||||||
return new MPT1327ID();
|
|
||||||
case NON_RECORDABLE:
|
|
||||||
return new NonRecordable();
|
|
||||||
case PRIORITY:
|
|
||||||
return new Priority();
|
|
||||||
case RADIO_ID:
|
|
||||||
return new Radio();
|
|
||||||
case RADIO_ID_RANGE:
|
|
||||||
return new RadioRange();
|
|
||||||
case RECORD:
|
|
||||||
return new Record();
|
|
||||||
case TALKGROUP:
|
|
||||||
return new Talkgroup();
|
|
||||||
case TALKGROUP_RANGE:
|
|
||||||
return new TalkgroupRange();
|
|
||||||
case SITE:
|
|
||||||
return new SiteID();
|
|
||||||
case STATUS:
|
|
||||||
return new UserStatusID();
|
|
||||||
case LEGACY_TALKGROUP:
|
|
||||||
return new LegacyTalkgroupID();
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AliasAction getAliasAction(AliasActionType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case BEEP:
|
|
||||||
return new BeepAction();
|
|
||||||
case CLIP:
|
|
||||||
return new ClipAction();
|
|
||||||
case SCRIPT:
|
|
||||||
return new ScriptAction();
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,6 +21,7 @@ package io.github.dsheirer.alias;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
@@ -32,6 +33,7 @@ import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
|||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
|
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
|
||||||
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
@@ -41,12 +43,8 @@ import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
|||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneSequence;
|
import io.github.dsheirer.identifier.tone.ToneSequence;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -57,6 +55,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
||||||
@@ -67,6 +69,7 @@ public class AliasList
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
||||||
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
|
private Map<DCSCode,Alias> mDCSCodeAliasMap = new EnumMap<>(DCSCode.class);
|
||||||
private Map<String,Alias> mESNMap = new HashMap<>();
|
private Map<String,Alias> mESNMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
||||||
@@ -178,6 +181,12 @@ public class AliasList
|
|||||||
|
|
||||||
radioRangeAliasList.add(radioRange, alias);
|
radioRangeAliasList.add(radioRange, alias);
|
||||||
break;
|
break;
|
||||||
|
case DCS:
|
||||||
|
if(id instanceof Dcs dcs)
|
||||||
|
{
|
||||||
|
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ESN:
|
case ESN:
|
||||||
String esn = ((Esn)id).getEsn();
|
String esn = ((Esn)id).getEsn();
|
||||||
|
|
||||||
@@ -379,6 +388,8 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PATCH_GROUP:
|
case PATCH_GROUP:
|
||||||
|
List<Alias> aliases = new ArrayList<>();
|
||||||
|
|
||||||
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
||||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
@@ -386,8 +397,6 @@ public class AliasList
|
|||||||
|
|
||||||
if(patchGroupAliasList != null)
|
if(patchGroupAliasList != null)
|
||||||
{
|
{
|
||||||
List<Alias> aliases = new ArrayList<>();
|
|
||||||
|
|
||||||
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
||||||
|
|
||||||
if(alias != null)
|
if(alias != null)
|
||||||
@@ -395,19 +404,36 @@ public class AliasList
|
|||||||
aliases.add(alias);
|
aliases.add(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers())
|
for(TalkgroupIdentifier patchedTalkgroup: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
{
|
{
|
||||||
Alias patchedAlias = patchGroupAliasList.getAlias(patchedGroup);
|
Alias patchedTalkgroupAlias = patchGroupAliasList.getAlias(patchedTalkgroup);
|
||||||
|
|
||||||
if(patchedAlias != null && !aliases.contains(patchedAlias))
|
if(patchedTalkgroupAlias != null && !aliases.contains(patchedTalkgroupAlias))
|
||||||
{
|
{
|
||||||
aliases.add(patchedAlias);
|
aliases.add(patchedTalkgroupAlias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return aliases;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
if(patchGroup.hasPatchedRadios())
|
||||||
|
{
|
||||||
|
RadioAliasList radioAliasList = mRadioProtocolMap.get(patchGroupIdentifier.getProtocol());
|
||||||
|
|
||||||
|
if(radioAliasList != null)
|
||||||
|
{
|
||||||
|
for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers())
|
||||||
|
{
|
||||||
|
Alias patchedRadioAlias = radioAliasList.getAlias(patchedRadio);
|
||||||
|
|
||||||
|
if(patchedRadioAlias != null && !aliases.contains(patchedRadioAlias))
|
||||||
|
{
|
||||||
|
aliases.add(patchedRadioAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliases;
|
||||||
case RADIO:
|
case RADIO:
|
||||||
RadioIdentifier radio = (RadioIdentifier)identifier;
|
RadioIdentifier radio = (RadioIdentifier)identifier;
|
||||||
|
|
||||||
@@ -418,7 +444,7 @@ public class AliasList
|
|||||||
return toList(radioAliasList.getAlias(radio));
|
return toList(radioAliasList.getAlias(radio));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ESN:
|
case DCS:
|
||||||
if(identifier instanceof ESNIdentifier)
|
if(identifier instanceof ESNIdentifier)
|
||||||
{
|
{
|
||||||
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
|
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
|
||||||
@@ -439,9 +465,9 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TONE:
|
case TONE:
|
||||||
if(identifier instanceof ToneIdentifier)
|
if(identifier instanceof ToneIdentifier toneIdentifier)
|
||||||
{
|
{
|
||||||
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
|
ToneSequence toneSequence = toneIdentifier.getValue();
|
||||||
|
|
||||||
if(toneSequence != null && toneSequence.hasTones())
|
if(toneSequence != null && toneSequence.hasTones())
|
||||||
{
|
{
|
||||||
@@ -454,6 +480,15 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(identifier instanceof DCSIdentifier dcsIdentifier)
|
||||||
|
{
|
||||||
|
DCSCode dcsCode = dcsIdentifier.getValue();
|
||||||
|
|
||||||
|
if(dcsCode != null)
|
||||||
|
{
|
||||||
|
return toList(mDCSCodeAliasMap.get(dcsCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,9 +500,7 @@ public class AliasList
|
|||||||
{
|
{
|
||||||
if(alias != null)
|
if(alias != null)
|
||||||
{
|
{
|
||||||
List<Alias> aliases = new ArrayList<>();
|
return Collections.singletonList(alias);
|
||||||
aliases.add(alias);
|
|
||||||
return aliases;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.alias.id;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
|
|||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
||||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
||||||
@@ -54,6 +52,7 @@ import javafx.util.Callback;
|
|||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
||||||
|
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
|
||||||
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
||||||
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
||||||
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.alias.id;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@ import java.util.EnumSet;
|
|||||||
public enum AliasIDType
|
public enum AliasIDType
|
||||||
{
|
{
|
||||||
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
||||||
|
DCS("Digital Coded Squelch (DCS)"),
|
||||||
ESN("ESN"),
|
ESN("ESN"),
|
||||||
INVERT("Audio Inversion"),
|
INVERT("Audio Inversion"),
|
||||||
LOJACK("LoJack"),
|
LOJACK("LoJack"),
|
||||||
|
|||||||
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.alias.id.dcs;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
|
import io.github.dsheirer.alias.id.AliasIDType;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digital Coded Squelch (DCS) tone identifier
|
||||||
|
*/
|
||||||
|
public class Dcs extends AliasID implements Comparable<Dcs>
|
||||||
|
{
|
||||||
|
private DCSCode mDCSCode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AliasIDType getType()
|
||||||
|
{
|
||||||
|
return AliasIDType.DCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(AliasID id)
|
||||||
|
{
|
||||||
|
if(isValid() && id instanceof Dcs other)
|
||||||
|
{
|
||||||
|
return other.isValid() && getDCSCode().equals(other.getDCSCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCS code
|
||||||
|
* @return DCS code or null.
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "code")
|
||||||
|
public DCSCode getDCSCode()
|
||||||
|
{
|
||||||
|
return mDCSCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the DCS code value.
|
||||||
|
* @param dcsCode to set
|
||||||
|
*/
|
||||||
|
public void setDCSCode(DCSCode dcsCode)
|
||||||
|
{
|
||||||
|
mDCSCode = dcsCode;
|
||||||
|
updateValueProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return mDCSCode != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAudioIdentifier()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if(isValid())
|
||||||
|
{
|
||||||
|
return getDCSCode().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "DCS-Invalid - No Tone Selected";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Dcs o)
|
||||||
|
{
|
||||||
|
if(isValid())
|
||||||
|
{
|
||||||
|
if(o.isValid())
|
||||||
|
{
|
||||||
|
return getDCSCode().compareTo(o.getDCSCode());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -43,6 +43,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||||
private static float[] sHighPassFilterCoefficients;
|
private static float[] sHighPassFilterCoefficients;
|
||||||
|
private final boolean mAudioFilterEnable;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
@@ -70,27 +71,33 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Audio Module.
|
* Creates an Audio Module.
|
||||||
*
|
*
|
||||||
* @param aliasList for aliasing identifiers
|
* @param aliasList for aliasing identifiers
|
||||||
|
* @param timeslot for this audio module
|
||||||
* @param maxAudioSegmentLength in milliseconds
|
* @param maxAudioSegmentLength in milliseconds
|
||||||
|
* @param audioFilterEnable to enable or disable high-pass audio filter
|
||||||
*/
|
*/
|
||||||
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
|
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, boolean audioFilterEnable)
|
||||||
{
|
{
|
||||||
super(aliasList, timeslot, maxAudioSegmentLength);
|
super(aliasList, timeslot, maxAudioSegmentLength);
|
||||||
|
mAudioFilterEnable = audioFilterEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Audio Module.
|
* Creates an Audio Module.
|
||||||
|
* @param aliasList for aliasing identifiers
|
||||||
|
* @param audioFilterEnable to enable or disable high-pass audio filter
|
||||||
*/
|
*/
|
||||||
public AudioModule(AliasList aliasList)
|
public AudioModule(AliasList aliasList, boolean audioFilterEnable)
|
||||||
{
|
{
|
||||||
super(aliasList);
|
super(aliasList);
|
||||||
|
mAudioFilterEnable = audioFilterEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,7 +128,11 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
{
|
{
|
||||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||||
{
|
{
|
||||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
if(mAudioFilterEnable)
|
||||||
|
{
|
||||||
|
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
addAudio(audioBuffer);
|
addAudio(audioBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,9 +38,6 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -48,12 +45,13 @@ import java.net.http.HttpClient;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
|
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
|
||||||
@@ -475,7 +473,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
|||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||||
{
|
{
|
||||||
sb.append(",").append(patched.getValue());
|
sb.append(",").append(patched.getValue());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
import com.google.common.net.HttpHeaders;
|
import com.google.common.net.HttpHeaders;
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import io.github.dsheirer.alias.Alias;
|
import io.github.dsheirer.alias.Alias;
|
||||||
import io.github.dsheirer.alias.AliasList;
|
import io.github.dsheirer.alias.AliasList;
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
@@ -34,7 +33,6 @@ import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
|||||||
import io.github.dsheirer.identifier.Form;
|
import io.github.dsheirer.identifier.Form;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
|
||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
|
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
@@ -42,24 +40,12 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
@@ -67,6 +53,8 @@ import java.util.concurrent.CompletionException;
|
|||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -525,7 +513,12 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
|
|||||||
|
|
||||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||||
|
|
||||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||||
{
|
{
|
||||||
sb.append(",").append(patched.getValue());
|
sb.append(",").append(patched.getValue());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,9 +24,7 @@ import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
|||||||
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
@@ -62,6 +60,11 @@ public class RdioScannerConfiguration extends BroadcastConfiguration
|
|||||||
mValid.unbind();
|
mValid.unbind();
|
||||||
mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
|
mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
|
||||||
Bindings.isNotNull(mHost)));
|
Bindings.isNotNull(mHost)));
|
||||||
|
|
||||||
|
if(mHost.getValue() == null || mHost.getValue().isEmpty())
|
||||||
|
{
|
||||||
|
mHost.set("http://localhost");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -276,8 +276,7 @@ public class BinaryMessage extends BitSet
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new BitSetFullException("bitset is full -- contains " +
|
throw new BitSetFullException("bitset is full -- contains [" + mPointer + "/" + size() + "] bits");
|
||||||
(mPointer + 1) + "bits");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,13 +844,18 @@ public class BinaryMessage extends BitSet
|
|||||||
*/
|
*/
|
||||||
public int getTwosComplement(int start, int end)
|
public int getTwosComplement(int start, int end)
|
||||||
{
|
{
|
||||||
BinaryMessage sub = getSubMessage(start, end);
|
if(get(start))
|
||||||
boolean negative = sub.get(start);
|
{
|
||||||
sub.flip(0, sub.size());
|
//Negative value - flip and add one
|
||||||
|
BinaryMessage fragment = getSubMessage(start, end);
|
||||||
int value = sub.getInt(0, sub.size()) + 1;
|
fragment.flip(0, fragment.size());
|
||||||
value *= (negative ? -1 : 1);
|
return fragment.getInt(1, fragment.size()) + 1;
|
||||||
return value;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Positive value - return the contents.
|
||||||
|
return getInt(start + 1, end);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -132,12 +132,10 @@ public enum SyncPattern
|
|||||||
true, true, false, false //1100 0xC
|
true, true, false, false //1100 0xC
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/* Sync (0x158) = 1101011000 */
|
/* Sync (0x158) = 101011000 */
|
||||||
PASSPORT( new boolean[]
|
PASSPORT( new boolean[]
|
||||||
{
|
{
|
||||||
true, //0001 0x1
|
true,false,true,false,true,true,false,false,false
|
||||||
false, true, false, true, //0101 0x5
|
|
||||||
true, false, false, false //1000 0x8
|
|
||||||
} ),
|
} ),
|
||||||
|
|
||||||
LTR_STANDARD_OSW( new boolean[]
|
LTR_STANDARD_OSW( new boolean[]
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ package io.github.dsheirer.buffer;
|
|||||||
import io.github.dsheirer.sample.SampleUtils;
|
import io.github.dsheirer.sample.SampleUtils;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,6 +32,7 @@ import java.util.Iterator;
|
|||||||
public class FloatNativeBuffer extends AbstractNativeBuffer
|
public class FloatNativeBuffer extends AbstractNativeBuffer
|
||||||
{
|
{
|
||||||
private float[] mInterleavedComplexSamples;
|
private float[] mInterleavedComplexSamples;
|
||||||
|
private static final int BUFFER_SIZE = 2048;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -81,48 +84,51 @@ public class FloatNativeBuffer extends AbstractNativeBuffer
|
|||||||
|
|
||||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||||
{
|
{
|
||||||
private boolean mEmpty;
|
private int mBufferPointer = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
return !mEmpty;
|
return mBufferPointer < mInterleavedComplexSamples.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComplexSamples next()
|
public ComplexSamples next()
|
||||||
{
|
{
|
||||||
if(mEmpty)
|
if(!hasNext())
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("No more samples");
|
throw new IllegalStateException("No more samples");
|
||||||
}
|
}
|
||||||
|
|
||||||
mEmpty = true;
|
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
||||||
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
|
mBufferPointer += BUFFER_SIZE * 2;
|
||||||
|
|
||||||
|
return SampleUtils.deinterleave(chunk, getTimestamp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||||
{
|
{
|
||||||
private boolean mEmpty;
|
private int mBufferPointer = 0;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
return !mEmpty;
|
return mBufferPointer < mInterleavedComplexSamples.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InterleavedComplexSamples next()
|
public InterleavedComplexSamples next()
|
||||||
{
|
{
|
||||||
if(mEmpty)
|
if(!hasNext())
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("No more samples");
|
throw new IllegalStateException("No more samples");
|
||||||
}
|
}
|
||||||
|
|
||||||
mEmpty = true;
|
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
||||||
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
|
mBufferPointer += BUFFER_SIZE * 2;
|
||||||
|
|
||||||
|
return new InterleavedComplexSamples(chunk, getTimestamp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ import javax.swing.JPanel;
|
|||||||
|
|
||||||
public class NowPlayingPanel extends JPanel
|
public class NowPlayingPanel extends JPanel
|
||||||
{
|
{
|
||||||
private ChannelMetadataPanel mChannelMetadataPanel;
|
private final ChannelMetadataPanel mChannelMetadataPanel;
|
||||||
private ChannelDetailPanel mChannelDetailPanel;
|
private final ChannelDetailPanel mChannelDetailPanel;
|
||||||
private DecodeEventPanel mDecodeEventPanel;
|
private final DecodeEventPanel mDecodeEventPanel;
|
||||||
private MessageActivityPanel mMessageActivityPanel;
|
private final MessageActivityPanel mMessageActivityPanel;
|
||||||
private ChannelPowerPanel mChannelPowerPanel;
|
private final ChannelPowerPanel mChannelPowerPanel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
|
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.buffer.INativeBufferProvider;
|
|||||||
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
|
import io.github.dsheirer.log.LoggingSuppressor;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||||
@@ -65,6 +66,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class PolyphaseChannelManager implements ISourceEventProcessor
|
public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||||
{
|
{
|
||||||
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
|
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LoggerFactory.getLogger(PolyphaseChannelManager.class));
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||||
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
||||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||||
@@ -203,7 +205,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
catch(IllegalArgumentException iae)
|
catch(IllegalArgumentException iae)
|
||||||
{
|
{
|
||||||
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
|
LOGGING_SUPPRESSOR.error(iae.getMessage(), 3, "Couldn't allocate channel. " + iae.getMessage());
|
||||||
|
channelSource = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,61 +16,64 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.dsp.fsk;
|
package io.github.dsheirer.dsp.fsk;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.MessageFramer;
|
|
||||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||||
import io.github.dsheirer.dsp.filter.dc.IIRSinglePoleDCRemovalFilter;
|
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import java.util.Arrays;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logic Trunked Radio (LTR) 300-baud FSK decoder is designed to operate against an 8 kHz FM demodulated audio stream.
|
* LTR decoder designed to work with 8 kHz input audio samples and decode 300-baud signalling.
|
||||||
*
|
*
|
||||||
* This decoder employs a zero-crossing timing error detector with adjustable symbol timing error gain. This decoder
|
* This decoder monitors the incoming sample stream looking for either a positive or negative sequence of samples where
|
||||||
* also employs a synchronization monitor that automatically adjusts symbol timing error gain according to the quantity
|
* the energy delta across the baud period exceeds a threshold value and causes the symbol state to flip. The symbol
|
||||||
* of successful message sync pattern detections. This is optimized for LTR signals that periodically transmit idle
|
* state remains across successive baud periods until the next state change sequence is detected.
|
||||||
* bursts and transmit continuous message streams during voice transmissions. The synchronization monitor adjusts to an
|
|
||||||
* aggressive gain value when sync is lost and automatically reduces timing error gain as successive sync pattern
|
|
||||||
* detections occur.
|
|
||||||
*
|
*
|
||||||
* FM demodulated sample buffers should not be filtered prior to this decoder. An internal DC-removal filter coupled
|
* Valid LTR messages start with a zero symbol and transition to a positive symbol for the sync bit, this decoder
|
||||||
* with a low-pass filter removes any tuning offset and/or audio components above 300 Hertz. The DC removal filter is
|
* monitors for excessively long sequences of a one symbol and forces the symbol state back to a zero.
|
||||||
* adjusted for a slower offset removal in order to prevent overly distorting the FSK signalling component. This mild
|
|
||||||
* adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However,
|
|
||||||
* the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission.
|
|
||||||
*/
|
*/
|
||||||
public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISyncStateListener
|
public class LTRDecoder implements Listener<float[]>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
private static final Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
||||||
|
//Slope calculation sumXX value is always the same for 27 evenly spaced samples
|
||||||
public static final float SAMPLES_PER_SYMBOL = 8000.0f / 300.0f;
|
private static final double SLOPE_CALCULATION_SUM_XX = 1637.999992057681;
|
||||||
|
private static final float BAUD_LENGTH = 8000.0f / 300.0f;
|
||||||
protected static final float COARSE_TIMING_GAIN = 1.0f / 3.0f;
|
private static final float SLOPE_THRESHOLD = 0.0048f;
|
||||||
protected static final float MEDIUM_TIMING_GAIN = 1.0f / 4.0f;
|
private static final int MAX_ONES_SEQUENCE = 18;
|
||||||
protected static final float FINE_TIMING_GAIN = 1.0f / 5.0f;
|
private static final int OVERLAP = 26;
|
||||||
|
private static final int POST_TRANSITION_SAMPLES_TO_SKIP = 26;
|
||||||
|
private static final int SLOPE_CALCULATION_LENGTH = 27;
|
||||||
|
private static final int SYMBOL_TRANSITION_IDEAL_MIN = 11;
|
||||||
|
private static final int SYMBOL_TRANSITION_IDEAL_MAX = 16;
|
||||||
|
|
||||||
private static float[] sLowPassFilterCoefficients;
|
private static float[] sLowPassFilterCoefficients;
|
||||||
|
private boolean mSymbol = false;
|
||||||
|
private float[] mResidual = new float[OVERLAP];
|
||||||
|
private float mBaudCounter = 0f;
|
||||||
|
private float mMaxSlope = 0;
|
||||||
|
private int mExcessiveOneSequenceCounter = 0;
|
||||||
|
private Listener<boolean[]> mSymbolListener;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||||
.sampleRate(8000)
|
.sampleRate(8000)
|
||||||
.gridDensity(16)
|
.gridDensity(16)
|
||||||
.oddLength(true)
|
.oddLength(true)
|
||||||
.passBandCutoff(300)
|
.passBandCutoff(300)
|
||||||
.passBandAmplitude(1.0)
|
.passBandAmplitude(1.0)
|
||||||
.passBandRipple(0.01)
|
.passBandRipple(0.01)
|
||||||
.stopBandStart(500)
|
.stopBandStart(500)
|
||||||
.stopBandAmplitude(0.0)
|
.stopBandAmplitude(0.0)
|
||||||
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -87,143 +90,193 @@ public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISync
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected float mSymbolTimingGain = COARSE_TIMING_GAIN;
|
|
||||||
protected SampleBuffer mSampleBuffer;
|
|
||||||
protected ZeroCrossingErrorDetector mTimingErrorDetector = new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL);
|
|
||||||
protected SynchronizationMonitor mSynchronizationMonitor;
|
|
||||||
private IIRSinglePoleDCRemovalFilter mDCFilter = new IIRSinglePoleDCRemovalFilter(0.99999f);
|
|
||||||
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
|
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
|
||||||
private MessageFramer mMessageFramer;
|
|
||||||
|
|
||||||
private boolean mSampleDecision;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder
|
* Constructs an instance
|
||||||
*
|
|
||||||
* @param messageLength in symbols
|
|
||||||
*/
|
*/
|
||||||
public LTRDecoder(int messageLength)
|
public LTRDecoder()
|
||||||
{
|
{
|
||||||
this(messageLength, new SampleBuffer(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
|
|
||||||
new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder.
|
* Registers the listener to receive decoded symbols.
|
||||||
*
|
* @param listener to register
|
||||||
* @param messageLength in symbols
|
|
||||||
* @param sampleBuffer to use for storing sample decisions
|
|
||||||
* @param detector for symbol timing errors
|
|
||||||
*/
|
*/
|
||||||
public LTRDecoder(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector)
|
public void setListener(Listener<boolean[]> listener)
|
||||||
{
|
{
|
||||||
mSynchronizationMonitor = new SynchronizationMonitor(messageLength);
|
mSymbolListener = listener;
|
||||||
mSynchronizationMonitor.setListener(this);
|
|
||||||
mTimingErrorDetector = detector;
|
|
||||||
mSampleBuffer = sampleBuffer;
|
|
||||||
mSampleBuffer.setTimingGain(mSymbolTimingGain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the ISyncDetectedListener interface to be notified of message sync detection events.
|
* Processes the demodulated 8 kHz audio samples to extract the 300-baud LTR signalling and delivers the decoded
|
||||||
*
|
* symbol array to the registered listener.
|
||||||
* This allows the internal timing error detector to adjust symbol timing error gain levels according to the
|
* @param samples to demodulate.
|
||||||
* message synchronization state to quickly adjust to initial signal streams or to reduce gain levels once
|
|
||||||
* synchronization has been achieved.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
public void receive(float[] samples)
|
||||||
public void syncDetected(int bitErrors)
|
|
||||||
{
|
{
|
||||||
mSynchronizationMonitor.syncDetected(bitErrors);
|
if(mSymbolListener != null)
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void syncLost(int bitsProcessed)
|
|
||||||
{
|
|
||||||
//no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
|
|
||||||
* greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
|
|
||||||
* adjusted and then each of the symbols are decoded using a simple majority decision.
|
|
||||||
*
|
|
||||||
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void receive(float[] buffer)
|
|
||||||
{
|
|
||||||
float[] dcFiltered = mDCFilter.filter(buffer);
|
|
||||||
float[] lowPassFiltered = mLowPassFilter.filter(dcFiltered);
|
|
||||||
|
|
||||||
for(float sample : lowPassFiltered)
|
|
||||||
{
|
{
|
||||||
mSampleDecision = sample > 0.0;
|
float[] filtered = mLowPassFilter.filter(samples);
|
||||||
|
|
||||||
mSampleBuffer.receive(mSampleDecision);
|
int timingAdjust = 0;
|
||||||
mTimingErrorDetector.receive(mSampleDecision);
|
int samplesToSkip = 0;
|
||||||
|
|
||||||
if(mSampleBuffer.hasSymbol())
|
float[] buffer = new float[filtered.length + OVERLAP];
|
||||||
|
boolean[] symbols = new boolean[(int)((filtered.length + mBaudCounter) / BAUD_LENGTH)];
|
||||||
|
int symbolPointer = 0;
|
||||||
|
float slope = 0;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(mMessageFramer != null)
|
System.arraycopy(mResidual, 0, buffer, 0, mResidual.length);
|
||||||
|
System.arraycopy(filtered, 0, buffer, mResidual.length, filtered.length);
|
||||||
|
System.arraycopy(filtered, filtered.length - OVERLAP, mResidual, 0, OVERLAP);
|
||||||
|
|
||||||
|
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
|
||||||
{
|
{
|
||||||
mMessageFramer.process(mSampleBuffer.getSymbol());
|
//Don't calculate slope if we're within the baud period following last symbol transition
|
||||||
|
if(samplesToSkip > 0)
|
||||||
|
{
|
||||||
|
samplesToSkip--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slope = calculateSlope(buffer, bufferPointer);
|
||||||
|
|
||||||
|
if(mSymbol)
|
||||||
|
{
|
||||||
|
//Loop for crest of slope at lowest point below the negative slope threshold for transition
|
||||||
|
if(slope > mMaxSlope && mMaxSlope < -SLOPE_THRESHOLD)
|
||||||
|
{
|
||||||
|
mSymbol = false;
|
||||||
|
|
||||||
|
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 16 samples
|
||||||
|
if(mBaudCounter < SYMBOL_TRANSITION_IDEAL_MIN)
|
||||||
|
{
|
||||||
|
timingAdjust = 1;
|
||||||
|
}
|
||||||
|
else if(mBaudCounter > SYMBOL_TRANSITION_IDEAL_MAX)
|
||||||
|
{
|
||||||
|
timingAdjust = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timingAdjust = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBaudCounter += timingAdjust;
|
||||||
|
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
|
||||||
|
}
|
||||||
|
else if(slope < mMaxSlope)
|
||||||
|
{
|
||||||
|
mMaxSlope = slope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Loop for crest of slope at the highest point above the positive threshold
|
||||||
|
if(slope < mMaxSlope && mMaxSlope > SLOPE_THRESHOLD)
|
||||||
|
{
|
||||||
|
mSymbol = true;
|
||||||
|
|
||||||
|
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 16 samples
|
||||||
|
if(mBaudCounter < SYMBOL_TRANSITION_IDEAL_MIN)
|
||||||
|
{
|
||||||
|
timingAdjust = 1;
|
||||||
|
}
|
||||||
|
else if(mBaudCounter > SYMBOL_TRANSITION_IDEAL_MAX)
|
||||||
|
{
|
||||||
|
timingAdjust = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timingAdjust = 0;
|
||||||
|
}
|
||||||
|
mBaudCounter += timingAdjust;
|
||||||
|
samplesToSkip = 26 + timingAdjust;
|
||||||
|
}
|
||||||
|
else if(slope > mMaxSlope)
|
||||||
|
{
|
||||||
|
mMaxSlope = slope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mBaudCounter++;
|
||||||
|
|
||||||
|
if(mBaudCounter > BAUD_LENGTH)
|
||||||
|
{
|
||||||
|
//Expand symbols array length if needed to account for symbol timing adjustments
|
||||||
|
if(symbolPointer >= symbols.length)
|
||||||
|
{
|
||||||
|
symbols = Arrays.copyOf(symbols, symbols.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
symbols[symbolPointer++] = mSymbol;
|
||||||
|
|
||||||
|
mBaudCounter -= BAUD_LENGTH;
|
||||||
|
|
||||||
|
//Check for a continuous string of ones and flip the symbol state to zero.
|
||||||
|
if(mSymbol)
|
||||||
|
{
|
||||||
|
mExcessiveOneSequenceCounter++;
|
||||||
|
|
||||||
|
if(mExcessiveOneSequenceCounter > MAX_ONES_SEQUENCE) //Verify that this is the right threshold for a string of ones
|
||||||
|
{
|
||||||
|
mSymbol = false;
|
||||||
|
mMaxSlope = -1f;
|
||||||
|
mExcessiveOneSequenceCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mExcessiveOneSequenceCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.warn("Unexpected error while processing LTR samples", e);
|
||||||
|
}
|
||||||
|
|
||||||
mSampleBuffer.resetAndAdjust(-mTimingErrorDetector.getError());
|
//Truncate the symbols array if we didn't fill it completely, due to symbol timing adjustments.
|
||||||
|
if(symbolPointer != symbols.length)
|
||||||
mSynchronizationMonitor.increment();
|
{
|
||||||
|
mSymbolListener.receive(Arrays.copyOf(symbols, symbolPointer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSymbolListener.receive(symbols);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a listener to receive decoded LTR symbols.
|
* Calculates the slope of the 27 samples in the values array starting at the specified offset.
|
||||||
*
|
* @param samples to load from
|
||||||
* @param messageFramer to receive symbols.
|
* @param offset to the sample start for the slope calculation
|
||||||
*/
|
*/
|
||||||
public void setMessageFramer(MessageFramer messageFramer)
|
public float calculateSlope(float[] samples, int offset)
|
||||||
{
|
{
|
||||||
mMessageFramer = messageFramer;
|
/** mean of accumulated x & y values, used in updating formulas */
|
||||||
}
|
double xbar = 0;
|
||||||
|
double ybar = samples[offset];
|
||||||
|
|
||||||
/**
|
double sumXY = 0;
|
||||||
* Removes the symbol listener from receiving decoded LTR symbols.
|
double fact1, dx, dy;
|
||||||
*/
|
|
||||||
public void removeListener()
|
|
||||||
{
|
|
||||||
mMessageFramer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for(int x = 1; x < SLOPE_CALCULATION_LENGTH; x++)
|
||||||
/**
|
|
||||||
* Implements the ISyncStateListener interface to receive synchronization state events and adjust the symbol timing
|
|
||||||
* error gain levels on the internal timing error detector.
|
|
||||||
*
|
|
||||||
* Gain levels are defined relative to a unit gain of 1.0 over a number of symbol periods. LTR uses an initial
|
|
||||||
* ramp-up symbol reversal pattern of 0101 which provides three zero crossing detection opportunities for the timing
|
|
||||||
* error detector, therefore we react to a COARSE gain state to average timing error over 3 symbols.
|
|
||||||
*
|
|
||||||
* @param syncState from an external synchronization state monitor
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setSyncState(SyncState syncState)
|
|
||||||
{
|
|
||||||
switch(syncState)
|
|
||||||
{
|
{
|
||||||
case FINE:
|
fact1 = 1.0f + x;
|
||||||
mSymbolTimingGain = FINE_TIMING_GAIN;
|
dx = x - xbar;
|
||||||
break;
|
dy = samples[offset + x] - ybar;
|
||||||
case MEDIUM:
|
sumXY += dx * dy * (x / (1.0f + x));
|
||||||
mSymbolTimingGain = MEDIUM_TIMING_GAIN;
|
xbar += dx / fact1;
|
||||||
break;
|
ybar += dy / fact1;
|
||||||
case COARSE:
|
|
||||||
mSymbolTimingGain = COARSE_TIMING_GAIN;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized sync state level: " + syncState.name());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mSampleBuffer.setTimingGain(mSymbolTimingGain);
|
return (float)(sumXY / SLOPE_CALCULATION_SUM_XX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,30 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdr-trunk
|
* *****************************************************************************
|
||||||
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* 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.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.dsp.fsk;
|
package io.github.dsheirer.dsp.fsk;
|
||||||
|
|
||||||
public class LTRDecoderInstrumented extends LTRDecoder
|
public class LTRDecoderInstrumented extends LTRDecoder
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Instrumented extension to LTRDecoder
|
* Instrumented extension to LTRDecoder
|
||||||
*
|
|
||||||
* @param messageLength in symbols
|
|
||||||
*/
|
*/
|
||||||
public LTRDecoderInstrumented(int messageLength)
|
public LTRDecoderInstrumented()
|
||||||
{
|
{
|
||||||
super(messageLength, new SampleBufferInstrumented(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
|
super();
|
||||||
new ZeroCrossingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SampleBufferInstrumented getSampleBuffer()
|
|
||||||
{
|
|
||||||
return (SampleBufferInstrumented)mSampleBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ZeroCrossingErrorDetectorInstrumented getErrorDetector()
|
|
||||||
{
|
|
||||||
return (ZeroCrossingErrorDetectorInstrumented)mTimingErrorDetector;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -30,6 +30,7 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
|||||||
private IPhaseLockedLoop mPLL;
|
private IPhaseLockedLoop mPLL;
|
||||||
private Complex mReceivedSample = new Complex(0, 0);
|
private Complex mReceivedSample = new Complex(0, 0);
|
||||||
private Listener<T> mSymbolListener;
|
private Listener<T> mSymbolListener;
|
||||||
|
private boolean mRunning;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Phase Shift Keyed (PSK) demodulator
|
* Abstract Phase Shift Keyed (PSK) demodulator
|
||||||
@@ -42,6 +43,31 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
|||||||
mPLL = phaseLockedLoop;
|
mPLL = phaseLockedLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts this decoder and sets the running flag to true
|
||||||
|
*/
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
mRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops this decoder and sets the running flag to false.
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this demodulator is running and capable of processing samples
|
||||||
|
* @return true if running
|
||||||
|
*/
|
||||||
|
public boolean isRunning()
|
||||||
|
{
|
||||||
|
return mRunning;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the listener to receive symbol decisions from this demodulator
|
* Registers the listener to receive symbol decisions from this demodulator
|
||||||
*/
|
*/
|
||||||
@@ -90,7 +116,10 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
|||||||
|
|
||||||
for(int x = 0; x < i.length; x++)
|
for(int x = 0; x < i.length; x++)
|
||||||
{
|
{
|
||||||
receive(i[x], q[x]);
|
if(isRunning())
|
||||||
|
{
|
||||||
|
receive(i[x], q[x]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
|
||||||
|
* <p>
|
||||||
|
* See ETSI TS 102 361-1 B.2.2.1 and B.2.2.2
|
||||||
|
*/
|
||||||
|
public class BPTC_16_2
|
||||||
|
{
|
||||||
|
private static final int[] DEINTERLEAVE = new int[]{0, 24, 1, 25, 2, 26, 3, 27, 4, 28, 5, 29, 6, 30, 7, 31, 8, 16,
|
||||||
|
9, 17, 10, 18, 11, 19, 12, 20, 13, 21, 14, 22, 15, 23};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unscramble and perform FEC checks per paragraph B.2.2.1 for Non-Reverse Channel Single Burst
|
||||||
|
*
|
||||||
|
* @param message with 32 interleaved bits.
|
||||||
|
* @return descrambled and error checked message or null if the process fails or there are too many errors.
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage decodeShortBurst(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage deinterleaved = deinterleave(message);
|
||||||
|
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
|
||||||
|
|
||||||
|
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for even parity. Bits 0-15 should be the same as bits 16-31.
|
||||||
|
for(int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
if(deinterleaved.get(x) ^ deinterleaved.get(x + 16))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unscramble and perform FEC checks per paragraph B.2.2.2 for Reverse Channel Single Burst
|
||||||
|
*
|
||||||
|
* @param binaryMessage with 32 interleaved bits.
|
||||||
|
* @return descrambled and error checked message or null if the process fails or there are too many errors.
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage decodeReverseChannel(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage deinterleaved = deinterleave(message);
|
||||||
|
System.out.println(" DEINTER: " + deinterleaved.toHexString());
|
||||||
|
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
|
||||||
|
System.out.println(" DECODED: " + deinterleaved.toHexString());
|
||||||
|
System.out.println("FEC:" + fec);
|
||||||
|
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for odd parity. Bits 0-15 should be opposite of bits 16-31.
|
||||||
|
for(int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
if(deinterleaved.get(x) == deinterleaved.get(x + 16))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs deinterleave of the interleaved message.
|
||||||
|
*
|
||||||
|
* @param original to deinterleave
|
||||||
|
* @return deinterleaved message
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage original)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage delinterleaved = new CorrectedBinaryMessage(32);
|
||||||
|
for(int x = 0; x < 32; x++)
|
||||||
|
{
|
||||||
|
if(original.get(x))
|
||||||
|
{
|
||||||
|
delinterleaved.set(DEINTERLEAVE[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return delinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
String[] msgs = new String[]{"05030A03", "35003A00", "1C6D2C9E"};
|
||||||
|
|
||||||
|
for(String msg : msgs)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage original = new CorrectedBinaryMessage(BinaryMessage.loadHex(msg));
|
||||||
|
System.out.println("ORIGINAL: " + original.toHexString());
|
||||||
|
CorrectedBinaryMessage decoded = decodeReverseChannel(original);
|
||||||
|
System.out.println("-------------------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* SDR Trunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
package io.github.dsheirer.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.message.MessageDirection;
|
import io.github.dsheirer.message.MessageDirection;
|
||||||
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,6 +98,14 @@ public class CRCLTR
|
|||||||
|
|
||||||
return CRC.FAILED_CRC;
|
return CRC.FAILED_CRC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getCRCReason(BitSet msg, MessageDirection direction)
|
||||||
|
{
|
||||||
|
int calculated = getCalculatedChecksum( msg );
|
||||||
|
int transmitted = getTransmittedChecksum( msg );
|
||||||
|
return "CALC: " + Integer.toHexString(calculated).toUpperCase() +
|
||||||
|
" TRANS: " + Integer.toHexString(transmitted).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] getChecks()
|
public static byte[] getChecks()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -226,17 +226,19 @@ public class CRCUtil
|
|||||||
{
|
{
|
||||||
mLog.debug("Starting");
|
mLog.debug("Starting");
|
||||||
|
|
||||||
//DMR message
|
long poly = 0x13l;
|
||||||
// String raw = "100110010001000000001001000000001111011011010000011001010000000000000000000000001100001000001011";
|
long[] checksums = generate(32, 4, poly, 0, true);
|
||||||
String raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101001001111010001110";
|
|
||||||
mLog.debug(raw);
|
|
||||||
raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101000000000000000000";
|
|
||||||
BinaryMessage message = BinaryMessage.load(raw);
|
|
||||||
mLog.debug(message.toString());
|
|
||||||
|
|
||||||
long polynomial = 0x11021l;
|
StringBuilder sb = new StringBuilder();
|
||||||
decode(message, 0, 80, polynomial, 16);
|
sb.append("private static int[] CHECKSUMS = new int[]{");
|
||||||
mLog.debug(message.toString());
|
for(long checksum: checksums)
|
||||||
mLog.debug("Finished");
|
{
|
||||||
|
sb.append("0x").append(Long.toHexString(checksum).toUpperCase());
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("};");
|
||||||
|
|
||||||
|
System.out.println("Checksums:\n" + sb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
@@ -5,27 +24,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* -----------------------------------------------------------------------
|
|
||||||
* Galois24 decoder based on Hank Wallace's tutorial/algorithm located at:
|
|
||||||
* http://www.aqdi.com/golay.htm
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Galois 24/12/7 decoder
|
* Galois 24/12/7 decoder
|
||||||
*/
|
*/
|
||||||
@@ -160,4 +158,17 @@ public class Golay24
|
|||||||
|
|
||||||
return (checksum ^ calculated);
|
return (checksum ^ calculated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20"));
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0"));
|
||||||
|
CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00"));
|
||||||
|
|
||||||
|
System.out.println("M:" + bm.toHexString());
|
||||||
|
int a = Golay24.checkAndCorrect(bm, 0);
|
||||||
|
System.out.println("M:" + bm.toHexString());
|
||||||
|
|
||||||
|
System.out.println("A:" + a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.function.Function;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a message filter that passes (true) all messages
|
* Implements an all-pass filter that automatically passes/allows any presented object.
|
||||||
|
*
|
||||||
|
* A key type of String is used, but that makes no difference in how this filter functions.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "rawtypes" )
|
public class AllPassFilter<T> extends Filter<T, String>
|
||||||
public class AllPassFilter<T> extends Filter<T>
|
|
||||||
{
|
{
|
||||||
public AllPassFilter()
|
private static final String KEY = "Other/Unlisted";
|
||||||
|
private final AllPassKeyExtractor mKeyExtractor = new AllPassKeyExtractor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param name to display for this filter
|
||||||
|
*/
|
||||||
|
public AllPassFilter(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
add(new FilterElement<>(KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Function getKeyExtractor()
|
||||||
{
|
{
|
||||||
super( "Allow All Messages" );
|
return mKeyExtractor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean passes( T t )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean canProcess( T t )
|
* Key extractor that always returns the same (String)key.
|
||||||
{
|
*/
|
||||||
return true;
|
public class AllPassKeyExtractor implements Function<T,String>
|
||||||
}
|
{
|
||||||
|
@Override
|
||||||
@Override
|
public String apply(T t)
|
||||||
@SuppressWarnings( "unchecked" )
|
{
|
||||||
public List getFilterElements()
|
return KEY;
|
||||||
{
|
}
|
||||||
return Collections.EMPTY_LIST;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public abstract class Filter<T> implements IFilter<T>
|
/**
|
||||||
|
* Base filter class that uses an extractor function to extract a filter key (K) from an object of type T and then
|
||||||
|
* lookup the filter element (rule) indicating if objects of this type are enabled.
|
||||||
|
*
|
||||||
|
* @param <T> object type that this filter can process.
|
||||||
|
* @param <K> key type used to lookup filter elements
|
||||||
|
*/
|
||||||
|
public abstract class Filter<T,K> implements IFilter<T>
|
||||||
{
|
{
|
||||||
protected String mName;
|
private static final Logger LOG = LoggerFactory.getLogger(Filter.class);
|
||||||
protected boolean mEnabled = true;
|
private String mName;
|
||||||
|
private Map<K, FilterElement<K>> mFilterElementMap = new HashMap<>();
|
||||||
public Filter( String name )
|
private IFilterChangeListener mFilterChangeListener;
|
||||||
{
|
|
||||||
mName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this filter is enabled or disabled
|
* Constructs an instance
|
||||||
*/
|
* @param name of this filter
|
||||||
public boolean isEnabled()
|
*/
|
||||||
{
|
public Filter(String name)
|
||||||
return mEnabled;
|
{
|
||||||
}
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables (true) or disables (false) this filter. An enabled filter will
|
* Indicates if this filter contains a filter element matching the key value.
|
||||||
* evaluate messages and a disabled filter will return false on all message
|
* @param key to check
|
||||||
* test methods.
|
* @return true if this filter has a matching filter element.
|
||||||
*/
|
*/
|
||||||
public void setEnabled( boolean enabled )
|
protected boolean hasKey(K key)
|
||||||
{
|
{
|
||||||
mEnabled = enabled;
|
return mFilterElementMap.containsKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* List of filter elements managed by this filter
|
|
||||||
*/
|
|
||||||
public abstract List<FilterElement<?>> getFilterElements();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of this filter
|
* Key extractor function provided by the subclass.
|
||||||
*/
|
* @return key extractor function
|
||||||
public String getName()
|
*/
|
||||||
{
|
public abstract Function<T,K> getKeyExtractor();
|
||||||
return mName;
|
|
||||||
}
|
@Override
|
||||||
|
public boolean passes(T t)
|
||||||
public void setName( String name )
|
{
|
||||||
{
|
K key = getKeyExtractor().apply(t);
|
||||||
mName = name;
|
|
||||||
}
|
if(mFilterElementMap.containsKey(key))
|
||||||
|
{
|
||||||
public String toString()
|
return mFilterElementMap.get(key).isEnabled();
|
||||||
{
|
}
|
||||||
return mName;
|
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this filter can process the item T, meaning that it has a FilterElement that matches the key
|
||||||
|
* value for items of type T.
|
||||||
|
* @param t to test
|
||||||
|
* @return true if this filter can process item t
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean canProcess(T t)
|
||||||
|
{
|
||||||
|
return hasKey(getKeyExtractor().apply(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled()
|
||||||
|
{
|
||||||
|
for(FilterElement filterElement: mFilterElementMap.values())
|
||||||
|
{
|
||||||
|
if(filterElement.isEnabled())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the child filter element to this filter
|
||||||
|
* @param filterElement to add
|
||||||
|
*/
|
||||||
|
public void add(FilterElement<K> filterElement)
|
||||||
|
{
|
||||||
|
if(mFilterElementMap.containsKey(filterElement.getElement()))
|
||||||
|
{
|
||||||
|
LOG.warn("Filter element for key [" + filterElement.getElement() + "] named [" + filterElement.getName() +
|
||||||
|
"] already exists - overwriting existing value");
|
||||||
|
}
|
||||||
|
|
||||||
|
mFilterElementMap.put(filterElement.getElement(), filterElement);
|
||||||
|
filterElement.register(mFilterChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of filter elements managed by this filter
|
||||||
|
*/
|
||||||
|
public final List<FilterElement<K>> getFilterElements()
|
||||||
|
{
|
||||||
|
return new ArrayList<>(mFilterElementMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of this filter
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name of this filter
|
||||||
|
*
|
||||||
|
* @param name to set
|
||||||
|
*/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pretty version or name of this filter.
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of enabled child filter elements.
|
||||||
|
*
|
||||||
|
* @return count
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getEnabledCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for(FilterElement filterElement : mFilterElementMap.values())
|
||||||
|
{
|
||||||
|
if(filterElement.isEnabled())
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of child filter elements
|
||||||
|
*
|
||||||
|
* @return count
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getElementCount()
|
||||||
|
{
|
||||||
|
return mFilterElementMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a filter change listener on this filter and all filter elements.
|
||||||
|
* @param listener to be notified of any filter changes.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void register(IFilterChangeListener listener)
|
||||||
|
{
|
||||||
|
mFilterChangeListener = listener;
|
||||||
|
|
||||||
|
for(FilterElement child: mFilterElementMap.values())
|
||||||
|
{
|
||||||
|
child.register(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
src/main/java/io/github/dsheirer/filter/FilterEditor.java
Normal file
71
src/main/java/io/github/dsheirer/filter/FilterEditor.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter editor
|
||||||
|
* @param <T> item type for editing
|
||||||
|
*/
|
||||||
|
public class FilterEditor<T> extends JFrame
|
||||||
|
{
|
||||||
|
private FilterEditorPanel<T> mEditorPanel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param title for the editor window frame
|
||||||
|
* @param owner to register the popup location
|
||||||
|
* @param filterSet to use initially
|
||||||
|
*/
|
||||||
|
public FilterEditor(String title, Component owner, FilterSet<T> filterSet)
|
||||||
|
{
|
||||||
|
if(filterSet == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Unable to construct FilterEditor - FilterSet cannot be null");
|
||||||
|
}
|
||||||
|
setTitle(title);
|
||||||
|
setSize(600, 400);
|
||||||
|
setLocationRelativeTo(owner);
|
||||||
|
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||||
|
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][]"));
|
||||||
|
mEditorPanel = new FilterEditorPanel<>(filterSet);
|
||||||
|
JScrollPane scroller = new JScrollPane(mEditorPanel);
|
||||||
|
scroller.setViewportView(mEditorPanel);
|
||||||
|
add(scroller, "wrap");
|
||||||
|
JButton close = new JButton("Close");
|
||||||
|
close.addActionListener(e -> dispose());
|
||||||
|
add(close);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates this editor with the filterset.
|
||||||
|
* @param filterSet to use in this editor.
|
||||||
|
*/
|
||||||
|
public void updateFilterSet(FilterSet<T> filterSet)
|
||||||
|
{
|
||||||
|
mEditorPanel.updateFilterSet(filterSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,100 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.EventObject;
|
||||||
|
import java.util.List;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.JCheckBox;
|
import javax.swing.JCheckBox;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
|
||||||
import javax.swing.JTree;
|
import javax.swing.JTree;
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.event.CellEditorListener;
|
import javax.swing.event.CellEditorListener;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
import javax.swing.tree.DefaultTreeCellRenderer;
|
import javax.swing.tree.DefaultTreeCellRenderer;
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
import javax.swing.tree.TreeCellEditor;
|
import javax.swing.tree.TreeCellEditor;
|
||||||
|
import javax.swing.tree.TreeNode;
|
||||||
import javax.swing.tree.TreeSelectionModel;
|
import javax.swing.tree.TreeSelectionModel;
|
||||||
import java.awt.Component;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.awt.event.ItemEvent;
|
|
||||||
import java.awt.event.ItemListener;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.awt.event.MouseListener;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.EventObject;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor panel for managing the state of a filter set.
|
||||||
|
*
|
||||||
|
* @param <T> element type for the filter set.
|
||||||
|
*/
|
||||||
public class FilterEditorPanel<T> extends JPanel
|
public class FilterEditorPanel<T> extends JPanel
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private JTree mTree;
|
private JTree mTree;
|
||||||
private DefaultTreeModel mModel;
|
private DefaultTreeModel mModel;
|
||||||
private FilterSet<T> mFilterSet;
|
private FilterSet<T> mFilterSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param filterSet to manage
|
||||||
|
*/
|
||||||
public FilterEditorPanel(FilterSet<T> filterSet)
|
public FilterEditorPanel(FilterSet<T> filterSet)
|
||||||
{
|
{
|
||||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||||
|
|
||||||
mFilterSet = filterSet;
|
mFilterSet = filterSet;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the panel and the tree model.
|
||||||
|
*/
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet);
|
DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet);
|
||||||
|
|
||||||
mModel = new DefaultTreeModel(root);
|
mModel = new DefaultTreeModel(root);
|
||||||
|
|
||||||
addFilterSet(mFilterSet, root);
|
addFilterSet(mFilterSet, root);
|
||||||
|
|
||||||
mTree = new JTree(mModel);
|
mTree = new JTree(mModel);
|
||||||
mTree.setShowsRootHandles(true);
|
mTree.setShowsRootHandles(true);
|
||||||
|
mTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||||
mTree.addMouseListener(new MouseHandler());
|
|
||||||
|
|
||||||
mTree.getSelectionModel().setSelectionMode(
|
|
||||||
TreeSelectionModel.SINGLE_TREE_SELECTION);
|
|
||||||
|
|
||||||
mTree.setCellRenderer(new EditorTreeCellRenderer());
|
mTree.setCellRenderer(new EditorTreeCellRenderer());
|
||||||
|
mTree.setCellEditor(new FilterTreeCellEditor());
|
||||||
mTree.setCellEditor(new FilterTreeNodeEditor());
|
|
||||||
|
|
||||||
mTree.setEditable(true);
|
mTree.setEditable(true);
|
||||||
|
|
||||||
add(mTree);
|
add(mTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the filter set for this editor panel
|
||||||
|
* @param filterSet to use
|
||||||
|
*/
|
||||||
|
public void updateFilterSet(FilterSet<T> filterSet)
|
||||||
|
{
|
||||||
|
remove(mTree);
|
||||||
|
mFilterSet = filterSet;
|
||||||
|
init();
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the filter set as a child tree node to the parent.
|
||||||
|
* @param filterSet to add to the tree
|
||||||
|
* @param parent node for the filter set child tree node.
|
||||||
|
*/
|
||||||
private void addFilterSet(FilterSet<T> filterSet, DefaultMutableTreeNode parent)
|
private void addFilterSet(FilterSet<T> filterSet, DefaultMutableTreeNode parent)
|
||||||
{
|
{
|
||||||
List<IFilter<T>> filters = filterSet.getFilters();
|
List<IFilter<T>> filters = filterSet.getFilters();
|
||||||
|
|
||||||
/* sort the filters in alphabetical order by name */
|
|
||||||
filters.sort(Comparator.comparing(IFilter::getName));
|
|
||||||
|
|
||||||
for(IFilter<T> filter : filters)
|
for(IFilter<T> filter : filters)
|
||||||
{
|
{
|
||||||
DefaultMutableTreeNode child = new DefaultMutableTreeNode(filter);
|
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(filter);
|
||||||
|
mModel.insertNodeInto(childNode, parent, parent.getChildCount());
|
||||||
|
|
||||||
mModel.insertNodeInto(child, parent, parent.getChildCount());
|
if(filter instanceof FilterSet childFilterSet)
|
||||||
|
|
||||||
if(filter instanceof FilterSet)
|
|
||||||
{
|
{
|
||||||
addFilterSet((FilterSet<T>) filter, child);
|
addFilterSet(childFilterSet, childNode);
|
||||||
}
|
}
|
||||||
else if(filter instanceof Filter)
|
else if(filter instanceof Filter childFilter)
|
||||||
{
|
{
|
||||||
addFilter((Filter<T>) filter, child);
|
addFilter(childFilter, childNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFilter(Filter<T> filter, DefaultMutableTreeNode parent)
|
/**
|
||||||
|
* Adds the filter as a child tree node to the parent.
|
||||||
|
*
|
||||||
|
* @param filter to add
|
||||||
|
* @param parent tree node
|
||||||
|
*/
|
||||||
|
private void addFilter(Filter filter, DefaultMutableTreeNode parent)
|
||||||
{
|
{
|
||||||
List<FilterElement<?>> elements = filter.getFilterElements();
|
List<FilterElement<?>> elements = filter.getFilterElements();
|
||||||
|
|
||||||
@@ -103,123 +131,39 @@ public class FilterEditorPanel<T> extends JPanel
|
|||||||
for(FilterElement<?> element : elements)
|
for(FilterElement<?> element : elements)
|
||||||
{
|
{
|
||||||
DefaultMutableTreeNode child = new DefaultMutableTreeNode(element);
|
DefaultMutableTreeNode child = new DefaultMutableTreeNode(element);
|
||||||
|
|
||||||
mModel.insertNodeInto(child, parent, parent.getChildCount());
|
mModel.insertNodeInto(child, parent, parent.getChildCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MouseHandler implements MouseListener
|
/**
|
||||||
|
* Accesses the tree's root tree node.
|
||||||
|
* @return root node.
|
||||||
|
*/
|
||||||
|
private DefaultMutableTreeNode getRoot()
|
||||||
{
|
{
|
||||||
public MouseHandler()
|
if(mTree.getModel().getRoot() instanceof DefaultMutableTreeNode root)
|
||||||
{
|
{
|
||||||
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
return null;
|
||||||
public void mouseClicked(MouseEvent event)
|
|
||||||
{
|
|
||||||
if(SwingUtilities.isRightMouseButton(event))
|
|
||||||
{
|
|
||||||
int row = mTree.getRowForLocation(event.getX(),
|
|
||||||
event.getY());
|
|
||||||
|
|
||||||
if(row != -1)
|
|
||||||
{
|
|
||||||
mTree.setSelectionRow(row);
|
|
||||||
|
|
||||||
Object selectedNode =
|
|
||||||
mTree.getLastSelectedPathComponent();
|
|
||||||
|
|
||||||
if(selectedNode instanceof DefaultMutableTreeNode)
|
|
||||||
{
|
|
||||||
final DefaultMutableTreeNode node =
|
|
||||||
(DefaultMutableTreeNode) selectedNode;
|
|
||||||
|
|
||||||
if(node.getChildCount() > 0)
|
|
||||||
{
|
|
||||||
JPopupMenu selectionMenu = new JPopupMenu();
|
|
||||||
|
|
||||||
JMenuItem selectAll = new JMenuItem("Select All");
|
|
||||||
selectAll.addActionListener(new ActionListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent arg0)
|
|
||||||
{
|
|
||||||
setFilterEnabled(mModel, node, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
selectionMenu.add(selectAll);
|
|
||||||
|
|
||||||
JMenuItem deselectAll = new JMenuItem("Deselect All");
|
|
||||||
deselectAll.addActionListener(new ActionListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent arg0)
|
|
||||||
{
|
|
||||||
setFilterEnabled(mModel, node, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
selectionMenu.add(deselectAll);
|
|
||||||
|
|
||||||
selectionMenu.show(mTree,
|
|
||||||
event.getX(),
|
|
||||||
event.getY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mousePressed(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseReleased(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseEntered(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseExited(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively sets the enabled state on all filter node children below the
|
* Recursively sets the enabled state on all filter node children below the specified parent node.
|
||||||
* specified parent node.
|
|
||||||
*
|
*
|
||||||
* @param model - model containing the tree nodes
|
* @param model - model containing the tree nodes
|
||||||
* @param node - parent node
|
* @param node - parent node
|
||||||
* @param enabled - true or false to enable or disable the filters
|
* @param enabled - true or false to enable or disable the filters
|
||||||
*/
|
*/
|
||||||
private void setFilterEnabled(DefaultTreeModel model,
|
private void setFilterEnabled(DefaultTreeModel model, DefaultMutableTreeNode node, boolean enabled)
|
||||||
DefaultMutableTreeNode node,
|
|
||||||
boolean enabled)
|
|
||||||
{
|
{
|
||||||
Object obj = node.getUserObject();
|
Object obj = node.getUserObject();
|
||||||
|
|
||||||
if(obj instanceof FilterSet<?>)
|
if(obj instanceof FilterElement filterElement)
|
||||||
{
|
{
|
||||||
((FilterSet<?>) obj).setEnabled(enabled);
|
filterElement.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
else if(obj instanceof Filter)
|
|
||||||
{
|
|
||||||
((Filter<?>) obj).setEnabled(enabled);
|
|
||||||
}
|
|
||||||
else if(obj instanceof FilterElement)
|
|
||||||
{
|
|
||||||
((FilterElement<?>) obj).setEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
model.nodeChanged(node);
|
|
||||||
|
|
||||||
/* Recursively set the children of this node */
|
/* Recursively set the children of this node */
|
||||||
Enumeration<?> children = node.children();
|
Enumeration<?> children = node.children();
|
||||||
@@ -228,43 +172,123 @@ public class FilterEditorPanel<T> extends JPanel
|
|||||||
{
|
{
|
||||||
Object child = children.nextElement();
|
Object child = children.nextElement();
|
||||||
|
|
||||||
if(child instanceof DefaultMutableTreeNode)
|
if(child instanceof DefaultMutableTreeNode childNode)
|
||||||
{
|
{
|
||||||
setFilterEnabled(model, (DefaultMutableTreeNode) child, enabled);
|
setFilterEnabled(model, childNode, enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.nodeChanged(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively searches the tree to find the tree node containing the specified filter element.
|
||||||
|
*
|
||||||
|
* @param node to start search
|
||||||
|
* @param element to find
|
||||||
|
*/
|
||||||
|
private DefaultMutableTreeNode findFilterElementNode(DefaultMutableTreeNode node, FilterElement<?> element)
|
||||||
|
{
|
||||||
|
if(node != null)
|
||||||
|
{
|
||||||
|
if(node.getUserObject() instanceof FilterElement<?> selfFilterElement && selfFilterElement.equals(element))
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int x = 0; x < node.getChildCount(); x++)
|
||||||
|
{
|
||||||
|
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode found = findFilterElementNode(child, element);
|
||||||
|
|
||||||
|
if(found != null)
|
||||||
|
{
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the display value for the specified node and all parents in the nodes parentage.
|
||||||
|
* @param node to update
|
||||||
|
*/
|
||||||
|
private void updateParentage(DefaultMutableTreeNode node)
|
||||||
|
{
|
||||||
|
mModel.nodeChanged(node);
|
||||||
|
|
||||||
|
TreeNode parent = node.getParent();
|
||||||
|
|
||||||
|
while(parent != null)
|
||||||
|
{
|
||||||
|
mModel.nodeChanged(parent);
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively searches the tree to find the tree node containing the specified filter.
|
||||||
|
*
|
||||||
|
* @param node to start search
|
||||||
|
* @param filter to find
|
||||||
|
*/
|
||||||
|
private DefaultMutableTreeNode findFilterNode(DefaultMutableTreeNode node, IFilter<?> filter)
|
||||||
|
{
|
||||||
|
if(node != null)
|
||||||
|
{
|
||||||
|
if(node.getUserObject() instanceof IFilter<?> selfFilter && selfFilter.equals(filter))
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int x = 0; x < node.getChildCount(); x++)
|
||||||
|
{
|
||||||
|
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode found = findFilterNode(child, filter);
|
||||||
|
|
||||||
|
if(found != null)
|
||||||
|
{
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.println("Can't evaluate - node is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom cell renderer
|
||||||
|
*/
|
||||||
public class EditorTreeCellRenderer extends DefaultTreeCellRenderer
|
public class EditorTreeCellRenderer extends DefaultTreeCellRenderer
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
public Component getTreeCellRendererComponent(JTree tree,
|
public Component getTreeCellRendererComponent(JTree tree, Object treeNode, boolean selected, boolean expanded,
|
||||||
Object treeNode,
|
boolean leaf, int row, boolean hasFocus)
|
||||||
boolean selected,
|
|
||||||
boolean expanded,
|
|
||||||
boolean leaf,
|
|
||||||
int row,
|
|
||||||
boolean hasFocus)
|
|
||||||
{
|
{
|
||||||
if(treeNode instanceof DefaultMutableTreeNode)
|
if(treeNode instanceof DefaultMutableTreeNode)
|
||||||
{
|
{
|
||||||
Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject();
|
Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject();
|
||||||
|
|
||||||
JCheckBox checkBox = null;
|
JCheckBox checkBox = null;
|
||||||
|
|
||||||
if(userObject instanceof IFilter)
|
if(userObject instanceof IFilter filter)
|
||||||
{
|
{
|
||||||
IFilter<?> filter = (IFilter<?>) userObject;
|
checkBox = new FilterCheckBox(filter);
|
||||||
|
|
||||||
checkBox = new JCheckBox(filter.getName());
|
|
||||||
checkBox.setSelected(filter.isEnabled());
|
checkBox.setSelected(filter.isEnabled());
|
||||||
}
|
}
|
||||||
else if(userObject instanceof FilterElement)
|
else if(userObject instanceof FilterElement element)
|
||||||
{
|
{
|
||||||
FilterElement<?> element = (FilterElement<?>) userObject;
|
checkBox = new FilterElementCheckBox(element);
|
||||||
|
|
||||||
checkBox = new JCheckBox(element.getName());
|
|
||||||
checkBox.setSelected(element.isEnabled());
|
checkBox.setSelected(element.isEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,95 +309,110 @@ public class FilterEditorPanel<T> extends JPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getTreeCellRendererComponent(tree, treeNode, selected,
|
return super.getTreeCellRendererComponent(tree, treeNode, selected, expanded, leaf, row, hasFocus);
|
||||||
expanded, leaf, row, hasFocus);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box used in filter tree nodes
|
||||||
|
*/
|
||||||
public class FilterCheckBox extends JCheckBox
|
public class FilterCheckBox extends JCheckBox
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private IFilter<?> mFilter;
|
||||||
|
|
||||||
private IFilter<T> mFilter;
|
/**
|
||||||
|
* Constructs an instance
|
||||||
public FilterCheckBox(IFilter<T> filter)
|
*
|
||||||
|
* @param filter
|
||||||
|
*/
|
||||||
|
public FilterCheckBox(IFilter<?> filter)
|
||||||
{
|
{
|
||||||
super(filter.getName());
|
|
||||||
|
|
||||||
mFilter = filter;
|
mFilter = filter;
|
||||||
|
|
||||||
setSelected(mFilter.isEnabled());
|
setSelected(mFilter.isEnabled());
|
||||||
|
addItemListener(e -> {
|
||||||
|
DefaultMutableTreeNode selfNode = findFilterNode(getRoot(), mFilter);
|
||||||
|
|
||||||
addItemListener(new ItemListener()
|
if(selfNode != null)
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void itemStateChanged(ItemEvent e)
|
|
||||||
{
|
{
|
||||||
mFilter.setEnabled(FilterCheckBox.this.isSelected());
|
setFilterEnabled(mModel, selfNode, FilterCheckBox.this.isSelected());
|
||||||
|
updateLabel();
|
||||||
|
updateParentage(selfNode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelected(boolean b)
|
||||||
|
{
|
||||||
|
super.setSelected(b);
|
||||||
|
updateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLabel()
|
||||||
|
{
|
||||||
|
setText(mFilter.getName() + " (" + mFilter.getEnabledCount() + "/" + mFilter.getElementCount() + ")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box used in filter element tree nodes
|
||||||
|
*/
|
||||||
public class FilterElementCheckBox extends JCheckBox
|
public class FilterElementCheckBox extends JCheckBox
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private FilterElement<?> mFilter;
|
private FilterElement<?> mFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param filter element
|
||||||
|
*/
|
||||||
public FilterElementCheckBox(FilterElement<?> filter)
|
public FilterElementCheckBox(FilterElement<?> filter)
|
||||||
{
|
{
|
||||||
super(filter.getName());
|
super(filter.getName());
|
||||||
|
|
||||||
mFilter = filter;
|
mFilter = filter;
|
||||||
|
|
||||||
setSelected(mFilter.isEnabled());
|
setSelected(mFilter.isEnabled());
|
||||||
|
addItemListener(arg0 -> {
|
||||||
addItemListener(new ItemListener()
|
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
|
||||||
{
|
DefaultMutableTreeNode self = findFilterElementNode(getRoot(), mFilter);
|
||||||
@Override
|
if(self != null)
|
||||||
public void itemStateChanged(ItemEvent arg0)
|
|
||||||
{
|
{
|
||||||
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
|
updateParentage(self);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FilterTreeNodeEditor implements TreeCellEditor
|
/**
|
||||||
|
* Editor for filter tree nodes
|
||||||
|
*/
|
||||||
|
public class FilterTreeCellEditor implements TreeCellEditor
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
public Component getTreeCellEditorComponent(JTree tree, Object node, boolean isSelected, boolean expanded, boolean leaf, int row)
|
||||||
public Component getTreeCellEditorComponent(JTree tree, Object node,
|
|
||||||
boolean isSelected, boolean expanded, boolean leaf, int row)
|
|
||||||
{
|
{
|
||||||
if(node instanceof DefaultMutableTreeNode)
|
if(node instanceof DefaultMutableTreeNode mutableTreeNode)
|
||||||
{
|
{
|
||||||
Object userObject = ((DefaultMutableTreeNode) node).getUserObject();
|
Object userObject = mutableTreeNode.getUserObject();
|
||||||
|
|
||||||
if(userObject instanceof IFilter)
|
if(userObject instanceof IFilter filter)
|
||||||
{
|
{
|
||||||
return new FilterCheckBox((IFilter) userObject);
|
return new FilterCheckBox(filter);
|
||||||
}
|
}
|
||||||
else if(userObject instanceof FilterElement)
|
else if(userObject instanceof FilterElement filterElement)
|
||||||
{
|
{
|
||||||
return new FilterElementCheckBox((FilterElement<?>) userObject);
|
return new FilterElementCheckBox(filterElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JLabel(node.toString());
|
return new JLabel(node.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCellEditorListener(CellEditorListener l)
|
public void addCellEditorListener(CellEditorListener l) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelCellEditing()
|
public void cancelCellEditing() {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getCellEditorValue()
|
public Object getCellEditorValue()
|
||||||
@@ -388,9 +427,7 @@ public class FilterEditorPanel<T> extends JPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeCellEditorListener(CellEditorListener l)
|
public void removeCellEditorListener(CellEditorListener l) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldSelectCell(EventObject anEvent)
|
public boolean shouldSelectCell(EventObject anEvent)
|
||||||
|
|||||||
@@ -1,55 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter element provides filtering and enabled state tracking for an individual filterable element.
|
||||||
|
*
|
||||||
|
* @param <T> element to track and filter.
|
||||||
|
*/
|
||||||
public class FilterElement<T> implements Comparable<FilterElement<T>>
|
public class FilterElement<T> implements Comparable<FilterElement<T>>
|
||||||
{
|
{
|
||||||
private boolean mEnabled;
|
private boolean mEnabled;
|
||||||
private T mElement;
|
private final T mElement;
|
||||||
|
private IFilterChangeListener mFilterChangeListener;
|
||||||
public FilterElement( T element, boolean enabled )
|
|
||||||
{
|
|
||||||
mElement = element;
|
|
||||||
mEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterElement( T t )
|
|
||||||
{
|
|
||||||
this( t, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getElement()
|
|
||||||
{
|
|
||||||
return mElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled()
|
|
||||||
{
|
|
||||||
return mEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnabled( boolean enabled )
|
|
||||||
{
|
|
||||||
mEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return mElement.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public int compareTo( FilterElement<T> other )
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param element to track
|
||||||
|
* @param enabled enabled state of this filter element
|
||||||
|
*/
|
||||||
|
public FilterElement(T element, boolean enabled)
|
||||||
{
|
{
|
||||||
return toString().compareTo( other.toString() );
|
mElement = element;
|
||||||
|
mEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance with a default state of enabled (true).
|
||||||
|
*
|
||||||
|
* @param t element to track
|
||||||
|
*/
|
||||||
|
public FilterElement(T t)
|
||||||
|
{
|
||||||
|
this(t, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener with each child filter element.
|
||||||
|
*
|
||||||
|
* @param listener to register
|
||||||
|
*/
|
||||||
|
public void register(IFilterChangeListener listener)
|
||||||
|
{
|
||||||
|
mFilterChangeListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element being filtered.
|
||||||
|
*
|
||||||
|
* @return element
|
||||||
|
*/
|
||||||
|
public T getElement()
|
||||||
|
{
|
||||||
|
return mElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name or display string for the element.
|
||||||
|
*
|
||||||
|
* @return string version of the element.
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this filter is enabled indicating that elements of this type will pass this filter.
|
||||||
|
*
|
||||||
|
* @return enabled state.
|
||||||
|
*/
|
||||||
|
public boolean isEnabled()
|
||||||
|
{
|
||||||
|
return mEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the enabled state of this filter.
|
||||||
|
*
|
||||||
|
* @param enabled true to allow this filter's element type to pass the filter.
|
||||||
|
*/
|
||||||
|
public void setEnabled(boolean enabled)
|
||||||
|
{
|
||||||
|
mEnabled = enabled;
|
||||||
|
|
||||||
|
if(mFilterChangeListener != null)
|
||||||
|
{
|
||||||
|
mFilterChangeListener.filterChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the tracked element.
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return mElement.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements compareto interface for ordering of filter elements.
|
||||||
|
*
|
||||||
|
* @param other the object to be compared.
|
||||||
|
* @return comparison value
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public int compareTo(FilterElement<T> other)
|
||||||
if (!(o instanceof FilterElement)) return false;
|
{
|
||||||
return compareTo((FilterElement) o) == 0;
|
return toString().compareTo(other.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement equals interface for comparison of filter elements.
|
||||||
|
*
|
||||||
|
* @param o to compare
|
||||||
|
* @return comparison
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if(o instanceof FilterElement fe)
|
||||||
|
{
|
||||||
|
return compareTo(fe) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +1,239 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
import io.github.dsheirer.log.LoggingSuppressor;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter set is a parent container for filters.
|
||||||
|
*
|
||||||
|
* @param <T> type of element tracked/filtered by this filter set.
|
||||||
|
*/
|
||||||
public class FilterSet<T> implements IFilter<T>
|
public class FilterSet<T> implements IFilter<T>
|
||||||
{
|
{
|
||||||
protected List<IFilter<T>> mFilters = new ArrayList<IFilter<T>>();
|
private static final Logger LOGGER = LoggerFactory.getLogger(FilterSet.class);
|
||||||
protected boolean mEnabled = true;
|
private static LoggingSuppressor sLoggingSuppressor = new LoggingSuppressor(LOGGER);
|
||||||
protected String mName;
|
private List<IFilter<T>> mFilters = new ArrayList<>();
|
||||||
|
private String mName;
|
||||||
|
private IFilterChangeListener mFilterChangeListener;
|
||||||
|
|
||||||
public FilterSet( String name )
|
/**
|
||||||
{
|
* Constructs an instance of a filter set.
|
||||||
mName = name;
|
*
|
||||||
}
|
* @param name of this filter set.
|
||||||
|
*/
|
||||||
public FilterSet()
|
public FilterSet(String name)
|
||||||
{
|
|
||||||
mName = "Message Filter";
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterSet(IFilter<T> filter)
|
|
||||||
{
|
{
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance with a single filter using the default 'Filters' name.
|
||||||
|
*
|
||||||
|
* @param filter for this filter set.
|
||||||
|
*/
|
||||||
|
public FilterSet(IFilter<T> filter)
|
||||||
|
{
|
||||||
|
this("Filters");
|
||||||
addFilter(filter);
|
addFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName( String name )
|
|
||||||
{
|
|
||||||
mName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean passes( T t )
|
|
||||||
{
|
|
||||||
if( mEnabled )
|
|
||||||
{
|
|
||||||
for( IFilter<T> filter: mFilters )
|
|
||||||
{
|
|
||||||
// Make sure to loop through all filters.
|
|
||||||
// Only return if true or all filters have been evaluated.
|
|
||||||
if( filter.canProcess( t ) && filter.passes( t ))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean canProcess( T t )
|
* Registers the listener with each child filter element.
|
||||||
{
|
* @param listener to register
|
||||||
if( mEnabled )
|
*/
|
||||||
|
public void register(IFilterChangeListener listener)
|
||||||
|
{
|
||||||
|
mFilterChangeListener = listener;
|
||||||
|
|
||||||
|
for(IFilter child: mFilters)
|
||||||
{
|
{
|
||||||
for( IFilter<T> filter: mFilters )
|
child.register(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of this filter set.
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name of this filter set.
|
||||||
|
*
|
||||||
|
* @param name to set
|
||||||
|
*/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name for this filter set.
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the element passes at least one of the child filters in this filter set.
|
||||||
|
*
|
||||||
|
* @param t to evaluate
|
||||||
|
* @return true if the argument passes a child filter.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean passes(T t)
|
||||||
|
{
|
||||||
|
for(IFilter<T> filter : mFilters)
|
||||||
|
{
|
||||||
|
//Stop at the first filter that says that it can process the message type.
|
||||||
|
if(filter.canProcess(t))
|
||||||
{
|
{
|
||||||
if( filter.canProcess( t ) )
|
return filter.passes(t);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this filter set contains at least one filter that can evaluate the type of argument that is presented.
|
||||||
|
*
|
||||||
|
* @param t element to evaluate
|
||||||
|
* @return true if this filter set can evaluate objects of the argument's type.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean canProcess(T t)
|
||||||
|
{
|
||||||
|
for(IFilter<T> filter : mFilters)
|
||||||
|
{
|
||||||
|
if(filter.canProcess(t))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sLoggingSuppressor.info(t.getClass().getSimpleName(), 1, "FilterSet [" +
|
||||||
|
this.getClass().getSimpleName() + "] has no filter element for items of type [" +
|
||||||
|
t.getClass().getSimpleName() + "]");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Child filters
|
||||||
|
*
|
||||||
|
* @return filters
|
||||||
|
*/
|
||||||
public List<IFilter<T>> getFilters()
|
public List<IFilter<T>> getFilters()
|
||||||
{
|
{
|
||||||
return mFilters;
|
return new ArrayList<>(mFilters);
|
||||||
}
|
|
||||||
|
|
||||||
public void addFilters( List<IFilter<T>> filters )
|
|
||||||
{
|
|
||||||
mFilters.addAll( filters );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addFilter( IFilter<T> filter )
|
|
||||||
{
|
|
||||||
mFilters.add( filter );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeFilter( IFilter<T> filter )
|
|
||||||
{
|
|
||||||
mFilters.remove( filter );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean isEnabled()
|
* Adds a list of child filters to this filter set.
|
||||||
{
|
* @param filters to add
|
||||||
return mEnabled;
|
*/
|
||||||
}
|
public void addFilters(List<IFilter<T>> filters)
|
||||||
|
{
|
||||||
|
for(IFilter<T> filter: filters)
|
||||||
|
{
|
||||||
|
addFilter(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the child filter to this filter set.
|
||||||
|
* @param filter to add
|
||||||
|
*/
|
||||||
|
public void addFilter(IFilter<T> filter)
|
||||||
|
{
|
||||||
|
if(filter instanceof Filter<?,?> filterInstance && filterInstance.getKeyExtractor() == null)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Filter [" + filter.getClass() + "] has a null key extractor");
|
||||||
|
}
|
||||||
|
|
||||||
|
mFilters.add(filter);
|
||||||
|
filter.register(mFilterChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if any of the child filters contain at least one filter element that is enabled.
|
||||||
|
* @return enabled state
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled( boolean enabled )
|
public boolean isEnabled()
|
||||||
{
|
{
|
||||||
mEnabled = enabled;
|
for(IFilter filter: mFilters)
|
||||||
}
|
{
|
||||||
|
if(filter.isEnabled())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive count of enabled child filter elements.
|
||||||
|
* @return count
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getEnabledCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for(IFilter filter: mFilters)
|
||||||
|
{
|
||||||
|
count += filter.getEnabledCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive count of child filter elements.
|
||||||
|
* @return count
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getElementCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for(IFilter filter: mFilters)
|
||||||
|
{
|
||||||
|
count += filter.getElementCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter interface
|
||||||
|
*
|
||||||
|
* @param <T> type of filter
|
||||||
|
*/
|
||||||
public interface IFilter<T>
|
public interface IFilter<T>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Generic filter method.
|
* Generic filter method.
|
||||||
*
|
*
|
||||||
* @param message - message to filter
|
* @param t - message to filter
|
||||||
* @return - true if the message passes the filter
|
* @return - true if the message passes the filter
|
||||||
*/
|
*/
|
||||||
public boolean passes( T t );
|
boolean passes(T t);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the filter can process (filter) the object
|
* Indicates if the filter can process (filter) the object
|
||||||
*
|
*
|
||||||
* @param message - candidate message for filtering
|
* @param t - candidate message for filtering
|
||||||
*
|
* @return - true if the filter is capable of filtering the message
|
||||||
* @return - true if the filter is capable of filtering the message
|
*/
|
||||||
*/
|
boolean canProcess(T t);
|
||||||
public boolean canProcess( T t );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this filter is enabled to evaluate messages
|
* Indicates if this filter is enabled to evaluate messages
|
||||||
*/
|
*/
|
||||||
public boolean isEnabled();
|
boolean isEnabled();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the enabled state of the filter
|
* Display name for the filter
|
||||||
*/
|
*/
|
||||||
public void setEnabled( boolean enabled );
|
String getName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display name for the filter
|
* (Recursive) count of enabled child filter elements.
|
||||||
*/
|
*
|
||||||
public String getName();
|
* @return total enable count.
|
||||||
|
*/
|
||||||
|
int getEnabledCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Recursive) count of total child filter elements.
|
||||||
|
*
|
||||||
|
* @return total count of child filter elements.
|
||||||
|
*/
|
||||||
|
int getElementCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to be notified when the enabled state of any child filter elements is updated.
|
||||||
|
*
|
||||||
|
* @param listener to be notified
|
||||||
|
*/
|
||||||
|
void register(IFilterChangeListener listener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
public interface IFilterChangeListener
|
||||||
|
{
|
||||||
|
void filterChanged();
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,30 +14,36 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
import io.github.dsheirer.filter.Filter;
|
|
||||||
import io.github.dsheirer.filter.FilterElement;
|
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.message.SyncLossMessage;
|
import io.github.dsheirer.message.SyncLossMessage;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import java.util.Collections;
|
/**
|
||||||
import java.util.List;
|
* Filter for sync-loss messages
|
||||||
|
*/
|
||||||
public class SyncLossMessageFilter extends Filter<IMessage>
|
public class SyncLossMessageFilter extends Filter<IMessage,String>
|
||||||
{
|
{
|
||||||
|
private static final String SYNC_LOSS_KEY = "Sync-Loss";
|
||||||
|
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
public SyncLossMessageFilter()
|
public SyncLossMessageFilter()
|
||||||
{
|
{
|
||||||
super("Sync Loss");
|
super("Sync Loss Messages");
|
||||||
|
add(new FilterElement<>(SYNC_LOSS_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean passes(IMessage message)
|
public Function<IMessage, String> getKeyExtractor()
|
||||||
{
|
{
|
||||||
return mEnabled && canProcess(message);
|
return mKeyExtractor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,9 +52,20 @@ public class SyncLossMessageFilter extends Filter<IMessage>
|
|||||||
return message instanceof SyncLossMessage;
|
return message instanceof SyncLossMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public List<FilterElement<?>> getFilterElements()
|
* Key extractor
|
||||||
|
*/
|
||||||
|
private class KeyExtractor implements Function<IMessage,String>
|
||||||
{
|
{
|
||||||
return Collections.EMPTY_LIST;
|
@Override
|
||||||
|
public String apply(IMessage message)
|
||||||
|
{
|
||||||
|
if(message instanceof SyncLossMessage)
|
||||||
|
{
|
||||||
|
return SYNC_LOSS_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,8 +24,6 @@ import io.github.dsheirer.alias.AliasModel;
|
|||||||
import io.github.dsheirer.controller.channel.map.ChannelMap;
|
import io.github.dsheirer.controller.channel.map.ChannelMap;
|
||||||
import io.github.dsheirer.controller.channel.map.ChannelRange;
|
import io.github.dsheirer.controller.channel.map.ChannelRange;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
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.IconManager;
|
||||||
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
||||||
import io.github.dsheirer.gui.playlist.PlaylistEditor;
|
import io.github.dsheirer.gui.playlist.PlaylistEditor;
|
||||||
@@ -37,6 +35,8 @@ import io.github.dsheirer.gui.preference.PreferenceEditorType;
|
|||||||
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
|
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
|
||||||
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
||||||
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
||||||
|
import io.github.dsheirer.gui.viewer.RecordingViewer;
|
||||||
|
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.jmbe.JmbeEditor;
|
import io.github.dsheirer.jmbe.JmbeEditor;
|
||||||
import io.github.dsheirer.jmbe.JmbeEditorRequest;
|
import io.github.dsheirer.jmbe.JmbeEditorRequest;
|
||||||
@@ -67,7 +67,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
public static final String USER_PREFERENCES_EDITOR = "preferences";
|
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_CALIBRATION_DIALOG = "calibration.dialog";
|
||||||
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
|
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_RECORDING_VIEWER = "recording.viewer";
|
||||||
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
|
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_JMBE_EDITOR = "jmbe.editor";
|
||||||
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
|
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
|
||||||
@@ -82,14 +82,14 @@ public class JavaFxWindowManager extends Application
|
|||||||
private TunerManager mTunerManager;
|
private TunerManager mTunerManager;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private UserPreferencesEditor mUserPreferencesEditor;
|
private UserPreferencesEditor mUserPreferencesEditor;
|
||||||
private DMRRecordingViewer mDmrRecordingViewer;
|
private RecordingViewer mRecordingViewer;
|
||||||
|
|
||||||
private Stage mChannelMapStage;
|
private Stage mChannelMapStage;
|
||||||
private Stage mIconManagerStage;
|
private Stage mIconManagerStage;
|
||||||
private Stage mJmbeEditorStage;
|
private Stage mJmbeEditorStage;
|
||||||
private Stage mPlaylistStage;
|
private Stage mPlaylistStage;
|
||||||
private Stage mUserPreferencesStage;
|
private Stage mUserPreferencesStage;
|
||||||
private Stage mDmrRecordingViewerStage;
|
private Stage mRecordingViewerStage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance. Note: this constructor is used for Swing applications.
|
* Constructs an instance. Note: this constructor is used for Swing applications.
|
||||||
@@ -195,31 +195,31 @@ public class JavaFxWindowManager extends Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stage for the DMR Message Viewer
|
* Stage for the recording viewer
|
||||||
*/
|
*/
|
||||||
public Stage getDmrRecordingViewerStage()
|
public Stage getRecordingViewerStage()
|
||||||
{
|
{
|
||||||
if(mDmrRecordingViewerStage == null)
|
if(mRecordingViewerStage == null)
|
||||||
{
|
{
|
||||||
createJFXPanel();
|
createJFXPanel();
|
||||||
Scene scene = new Scene(getDmrRecordingViewer(), 1100, 800);
|
Scene scene = new Scene(getRecordingViewer(), 1100, 800);
|
||||||
mDmrRecordingViewerStage = new Stage();
|
mRecordingViewerStage = new Stage();
|
||||||
mDmrRecordingViewerStage.setTitle("sdrtrunk - DMR Recording Viewer");
|
mRecordingViewerStage.setTitle("sdrtrunk - Message Recording Viewer (.bits)");
|
||||||
mDmrRecordingViewerStage.setScene(scene);
|
mRecordingViewerStage.setScene(scene);
|
||||||
mUserPreferences.getJavaFxPreferences().monitor(mDmrRecordingViewerStage, STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER);
|
mUserPreferences.getJavaFxPreferences().monitor(mRecordingViewerStage, STAGE_MONITOR_KEY_RECORDING_VIEWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mDmrRecordingViewerStage;
|
return mRecordingViewerStage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DMRRecordingViewer getDmrRecordingViewer()
|
public RecordingViewer getRecordingViewer()
|
||||||
{
|
{
|
||||||
if(mDmrRecordingViewer == null)
|
if(mRecordingViewer == null)
|
||||||
{
|
{
|
||||||
mDmrRecordingViewer = new DMRRecordingViewer();
|
mRecordingViewer = new RecordingViewer();
|
||||||
}
|
}
|
||||||
|
|
||||||
return mDmrRecordingViewer;
|
return mRecordingViewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stage getIconManagerStage()
|
public Stage getIconManagerStage()
|
||||||
@@ -453,12 +453,12 @@ public class JavaFxWindowManager extends Application
|
|||||||
* Process a channel map editor request
|
* Process a channel map editor request
|
||||||
*/
|
*/
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void process(final ViewDmrRecordingViewerRequest request)
|
public void process(final ViewRecordingViewerRequest request)
|
||||||
{
|
{
|
||||||
execute(() -> {
|
execute(() -> {
|
||||||
getDmrRecordingViewerStage().show();
|
getRecordingViewerStage().show();
|
||||||
getDmrRecordingViewerStage().requestFocus();
|
getRecordingViewerStage().requestFocus();
|
||||||
getDmrRecordingViewerStage().toFront();
|
getRecordingViewerStage().toFront();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ import io.github.dsheirer.controller.channel.ChannelAutoStartFrame;
|
|||||||
import io.github.dsheirer.controller.channel.ChannelException;
|
import io.github.dsheirer.controller.channel.ChannelException;
|
||||||
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
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.icon.ViewIconManagerRequest;
|
||||||
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
||||||
import io.github.dsheirer.gui.preference.CalibrateRequest;
|
import io.github.dsheirer.gui.preference.CalibrateRequest;
|
||||||
import io.github.dsheirer.gui.preference.PreferenceEditorType;
|
import io.github.dsheirer.gui.preference.PreferenceEditorType;
|
||||||
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
||||||
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
||||||
|
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
|
||||||
import io.github.dsheirer.icon.IconModel;
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.log.ApplicationLog;
|
import io.github.dsheirer.log.ApplicationLog;
|
||||||
import io.github.dsheirer.map.MapService;
|
import io.github.dsheirer.map.MapService;
|
||||||
@@ -420,9 +420,9 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
|
|
||||||
viewMenu.add(new JSeparator());
|
viewMenu.add(new JSeparator());
|
||||||
|
|
||||||
JMenuItem dmrMessageViewerMenu = new JMenuItem("DMR Recording Viewer");
|
JMenuItem recordingViewerMenu = new JMenuItem("Message Recording Viewer (.bits)");
|
||||||
dmrMessageViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewDmrRecordingViewerRequest()));
|
recordingViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewRecordingViewerRequest()));
|
||||||
viewMenu.add(dmrMessageViewerMenu);
|
viewMenu.add(recordingViewerMenu);
|
||||||
|
|
||||||
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
||||||
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,22 +22,9 @@ import io.github.dsheirer.source.ISourceEventProcessor;
|
|||||||
import io.github.dsheirer.source.InvalidFrequencyException;
|
import io.github.dsheirer.source.InvalidFrequencyException;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import net.miginfocom.swing.MigLayout;
|
|
||||||
import org.apache.commons.math3.util.FastMath;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Cursor;
|
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.Image;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.awt.Toolkit;
|
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.KeyListener;
|
import java.awt.event.KeyListener;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
@@ -49,27 +36,39 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom frequency controller for displaying or editing frequency values in MHz.
|
||||||
|
*/
|
||||||
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
|
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
|
||||||
|
|
||||||
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
|
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
|
||||||
|
|
||||||
private Color mHighlightColor = Color.YELLOW;
|
private Color mHighlightColor = Color.YELLOW;
|
||||||
|
|
||||||
private long mFrequency;
|
private long mFrequency;
|
||||||
|
|
||||||
private Cursor mBlankCursor;
|
|
||||||
|
|
||||||
private HashMap<Integer,Digit> mDigits = new HashMap<Integer,Digit>();
|
private HashMap<Integer,Digit> mDigits = new HashMap<Integer,Digit>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
public JFrequencyControl()
|
public JFrequencyControl()
|
||||||
{
|
{
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the control/layout.
|
||||||
|
*/
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
setLayout(new MigLayout("insets 0 0 0 0", "[]0[]", ""));
|
setLayout(new MigLayout("insets 0 0 0 0", "[]0[]", ""));
|
||||||
@@ -104,19 +103,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a blank cursor to use when the mouse is over the digits
|
|
||||||
*/
|
|
||||||
//Create an empty byte array
|
|
||||||
byte[] imageByte = new byte[0];
|
|
||||||
|
|
||||||
//Create image for cursor using empty array
|
|
||||||
Image cursorImage = Toolkit.getDefaultToolkit().createImage(imageByte);
|
|
||||||
|
|
||||||
mBlankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "cursor");
|
|
||||||
revalidate();
|
revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the control.
|
||||||
|
* @param enabled true if this component should be enabled, false otherwise
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled(boolean enabled)
|
public void setEnabled(boolean enabled)
|
||||||
{
|
{
|
||||||
@@ -157,11 +150,18 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current frequency value for the control.
|
||||||
|
* @return frequency
|
||||||
|
*/
|
||||||
public long getFrequency()
|
public long getFrequency()
|
||||||
{
|
{
|
||||||
return mFrequency;
|
return mFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the frequency value according to the manually changed digit value.
|
||||||
|
*/
|
||||||
private void updateFrequency()
|
private void updateFrequency()
|
||||||
{
|
{
|
||||||
long frequency = 0;
|
long frequency = 0;
|
||||||
@@ -174,14 +174,15 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
mFrequency = frequency;
|
mFrequency = frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires a source changed event
|
||||||
|
* @throws SourceException of there's an error
|
||||||
|
*/
|
||||||
private void fireSourceChanged() throws SourceException
|
private void fireSourceChanged() throws SourceException
|
||||||
{
|
{
|
||||||
updateFrequency();
|
updateFrequency();
|
||||||
|
|
||||||
Iterator<ISourceEventProcessor> it = mProcessors.iterator();
|
Iterator<ISourceEventProcessor> it = mProcessors.iterator();
|
||||||
|
|
||||||
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
|
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
|
||||||
|
|
||||||
while(it.hasNext())
|
while(it.hasNext())
|
||||||
{
|
{
|
||||||
it.next().process(event);
|
it.next().process(event);
|
||||||
@@ -217,12 +218,21 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
mProcessors.remove(processor);
|
mProcessors.remove(processor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom single digit value editor.
|
||||||
|
*/
|
||||||
public class Digit extends JTextField
|
public class Digit extends JTextField
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private int mPower = 0;
|
private int mPower = 0;
|
||||||
private long mValue = 0;
|
private long mValue = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param position of the digit where a position of 1 is the ones unit of MHz and a position of -1 is the tenths
|
||||||
|
* unit of MHz.
|
||||||
|
* @throws ParseException if there is an error
|
||||||
|
*/
|
||||||
private Digit(int position) throws ParseException
|
private Digit(int position) throws ParseException
|
||||||
{
|
{
|
||||||
super("0");
|
super("0");
|
||||||
@@ -236,6 +246,11 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
this.addMouseWheelListener(listener);
|
this.addMouseWheelListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a custom tooltip for the current hover-over digit.
|
||||||
|
* @param power of the digit in units of 10
|
||||||
|
* @return custom tooptip.
|
||||||
|
*/
|
||||||
private static String getTooltip(int power)
|
private static String getTooltip(int power)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -288,23 +303,32 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
{
|
{
|
||||||
//Strip the digits higher than this one
|
//Strip the digits higher than this one
|
||||||
long lower = frequency % (long)(FastMath.pow(10, mPower + 1));
|
long lower = frequency % (long)(FastMath.pow(10, mPower + 1));
|
||||||
|
|
||||||
//Set the value to int value of dividing by 10 to this power
|
//Set the value to int value of dividing by 10 to this power
|
||||||
long value = (long)(lower / (long)(FastMath.pow(10, mPower)));
|
long value = (long)(lower / (long)(FastMath.pow(10, mPower)));
|
||||||
|
|
||||||
set(value, fireChangeEvent);
|
set(value, fireChangeEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency value.
|
||||||
|
* @return frequency value.
|
||||||
|
*/
|
||||||
public long getFrequency()
|
public long getFrequency()
|
||||||
{
|
{
|
||||||
return mValue * (long)FastMath.pow(10, mPower);
|
return mValue * (long)FastMath.pow(10, mPower);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the unit value and fires a change event
|
||||||
|
*/
|
||||||
public void increment()
|
public void increment()
|
||||||
{
|
{
|
||||||
increment(true);
|
increment(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the unit value and fires a change event
|
||||||
|
* @param fireChangeEvent true to fire an event
|
||||||
|
*/
|
||||||
public void increment(boolean fireChangeEvent)
|
public void increment(boolean fireChangeEvent)
|
||||||
{
|
{
|
||||||
if(isEnabled())
|
if(isEnabled())
|
||||||
@@ -313,11 +337,18 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrements the unit value and fires a change event.
|
||||||
|
*/
|
||||||
public void decrement()
|
public void decrement()
|
||||||
{
|
{
|
||||||
decrement(true);
|
decrement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrements the unit value and optionally fires a change event.
|
||||||
|
* @param fireChangeEvent set to true to fire an event
|
||||||
|
*/
|
||||||
public void decrement(boolean fireChangeEvent)
|
public void decrement(boolean fireChangeEvent)
|
||||||
{
|
{
|
||||||
if(isEnabled())
|
if(isEnabled())
|
||||||
@@ -349,7 +380,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
while(mValue < 0)
|
while(mValue < 0)
|
||||||
{
|
{
|
||||||
mValue += 10;
|
mValue += 10;
|
||||||
|
|
||||||
Digit nextHigherDigit = mDigits.get(mPower + 1);
|
Digit nextHigherDigit = mDigits.get(mPower + 1);
|
||||||
|
|
||||||
if(nextHigherDigit != null)
|
if(nextHigherDigit != null)
|
||||||
@@ -363,7 +393,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
while(mValue > 9)
|
while(mValue > 9)
|
||||||
{
|
{
|
||||||
mValue -= 10;
|
mValue -= 10;
|
||||||
|
|
||||||
Digit nextHigherDigit = mDigits.get(mPower + 1);
|
Digit nextHigherDigit = mDigits.get(mPower + 1);
|
||||||
|
|
||||||
if(nextHigherDigit != null)
|
if(nextHigherDigit != null)
|
||||||
@@ -403,10 +432,8 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(se instanceof InvalidFrequencyException)
|
if(se instanceof InvalidFrequencyException ife)
|
||||||
{
|
{
|
||||||
InvalidFrequencyException ife = (InvalidFrequencyException)se;
|
|
||||||
|
|
||||||
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
|
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
|
||||||
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
|
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
|
||||||
}
|
}
|
||||||
@@ -415,13 +442,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
|
|
||||||
|
|
||||||
setText(String.valueOf(mValue));
|
setText(String.valueOf(mValue));
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Listener implements KeyListener,
|
/**
|
||||||
MouseListener,
|
* Multiple Listener interface implementation
|
||||||
MouseWheelListener
|
*/
|
||||||
|
private class Listener implements KeyListener, MouseListener, MouseWheelListener
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void keyReleased(KeyEvent e)
|
public void keyReleased(KeyEvent e)
|
||||||
@@ -503,14 +530,9 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e)
|
public void keyPressed(KeyEvent e) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyTyped(KeyEvent e)
|
public void keyTyped(KeyEvent e) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e)
|
public void mouseClicked(MouseEvent e)
|
||||||
@@ -528,21 +550,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mousePressed(MouseEvent e)
|
public void mousePressed(MouseEvent e) {}
|
||||||
{
|
public void mouseReleased(MouseEvent e) {}
|
||||||
}
|
|
||||||
|
|
||||||
public void mouseReleased(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseEntered(MouseEvent e)
|
public void mouseEntered(MouseEvent e)
|
||||||
{
|
{
|
||||||
Digit.this.setBackground(mHighlightColor);
|
Digit.this.setBackground(mHighlightColor);
|
||||||
|
|
||||||
setCursor(mBlankCursor);
|
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,9 +564,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
|||||||
public void mouseExited(MouseEvent e)
|
public void mouseExited(MouseEvent e)
|
||||||
{
|
{
|
||||||
Digit.this.setBackground(Color.WHITE);
|
Digit.this.setBackground(Color.WHITE);
|
||||||
|
|
||||||
setCursor(Cursor.getDefaultCursor());
|
|
||||||
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdr-trunk
|
* *****************************************************************************
|
||||||
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* 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.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.gui.instrument.chart;
|
package io.github.dsheirer.gui.instrument.chart;
|
||||||
|
|
||||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||||
@@ -58,7 +61,7 @@ public class LTRNetSampleBufferChart extends LineChart
|
|||||||
|
|
||||||
mLTRNetDecoderInstrumented = decoder;
|
mLTRNetDecoderInstrumented = decoder;
|
||||||
decoder.bufferCount.addListener(new BufferChangeListener());
|
decoder.bufferCount.addListener(new BufferChangeListener());
|
||||||
decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBuffer(boolean[] buffer)
|
public void setBuffer(boolean[] buffer)
|
||||||
@@ -89,7 +92,7 @@ public class LTRNetSampleBufferChart extends LineChart
|
|||||||
@Override
|
@Override
|
||||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||||
{
|
{
|
||||||
setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
|
// setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
|
||||||
|
|
||||||
while(mSymbol.size() < 2)
|
while(mSymbol.size() < 2)
|
||||||
{
|
{
|
||||||
@@ -115,29 +118,29 @@ public class LTRNetSampleBufferChart extends LineChart
|
|||||||
mSymbolSamples.add(sample);
|
mSymbolSamples.add(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||||
int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||||
|
|
||||||
mSamplePoints.get(0).setXValue(pointer1);
|
// mSamplePoints.get(0).setXValue(pointer1);
|
||||||
mSamplePoints.get(1).setXValue(pointer2);
|
// mSamplePoints.get(1).setXValue(pointer2);
|
||||||
|
|
||||||
float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
|
// float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
|
||||||
float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
|
// float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
|
||||||
|
|
||||||
float start = pointer1 + samplesRemaining;
|
// float start = pointer1 + samplesRemaining;
|
||||||
float end = start + samplesPerSymbol;
|
// float end = start + samplesPerSymbol;
|
||||||
|
|
||||||
mSymbolSamples.get(0).setXValue(start);
|
// mSymbolSamples.get(0).setXValue(start);
|
||||||
mSymbolSamples.get(1).setXValue(end);
|
// mSymbolSamples.get(1).setXValue(end);
|
||||||
|
|
||||||
boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
|
// boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
|
||||||
int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
|
// int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
|
||||||
int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
|
// int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
|
||||||
|
|
||||||
mSymbol.get(0).setXValue(symbolStart);
|
// mSymbol.get(0).setXValue(symbolStart);
|
||||||
mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
|
// mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
|
||||||
mSymbol.get(1).setXValue(symbolEnd);
|
// mSymbol.get(1).setXValue(symbolEnd);
|
||||||
mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
|
// mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,20 +155,20 @@ public class LTRNetSampleBufferChart extends LineChart
|
|||||||
mZeroCrossing.add(sample);
|
mZeroCrossing.add(sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||||
int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||||
|
|
||||||
float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
|
// float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
|
||||||
float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
|
// float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
|
||||||
|
//
|
||||||
int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
|
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
|
||||||
reference--;
|
// reference--;
|
||||||
|
//
|
||||||
float start = reference - zeroCrossingIdeal;
|
// float start = reference - zeroCrossingIdeal;
|
||||||
float end = reference - detectedZeroCrossing;
|
// float end = reference - detectedZeroCrossing;
|
||||||
|
//
|
||||||
mZeroCrossing.get(0).setXValue(start);
|
// mZeroCrossing.get(0).setXValue(start);
|
||||||
mZeroCrossing.get(1).setXValue(end);
|
// mZeroCrossing.get(1).setXValue(end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdr-trunk
|
* *****************************************************************************
|
||||||
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* 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.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.gui.instrument.chart;
|
package io.github.dsheirer.gui.instrument.chart;
|
||||||
|
|
||||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||||
@@ -53,7 +56,7 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
|||||||
setData(observableList);
|
setData(observableList);
|
||||||
|
|
||||||
mLTRNetDecoderInstrumented = decoder;
|
mLTRNetDecoderInstrumented = decoder;
|
||||||
decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||||
decoder.bufferCount.addListener(new ChangeListener()
|
decoder.bufferCount.addListener(new ChangeListener()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@@ -84,13 +87,13 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
|||||||
|
|
||||||
private void updateBuffer()
|
private void updateBuffer()
|
||||||
{
|
{
|
||||||
boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||||
|
//
|
||||||
for(int x = 0; x < samples.length; x++)
|
// for(int x = 0; x < samples.length; x++)
|
||||||
{
|
// {
|
||||||
Data<Number,Number> sample = mCurrentSamples.get(x);
|
// Data<Number,Number> sample = mCurrentSamples.get(x);
|
||||||
sample.setYValue(samples[x] ? 0.7f : 0.3f);
|
// sample.setYValue(samples[x] ? 0.7f : 0.3f);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ErrorChangeListener implements ChangeListener
|
public class ErrorChangeListener implements ChangeListener
|
||||||
@@ -98,35 +101,35 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
|||||||
@Override
|
@Override
|
||||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||||
{
|
{
|
||||||
float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
|
// float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
|
||||||
|
//
|
||||||
boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||||
|
//
|
||||||
for(int x = 0; x < samples.length; x++)
|
// for(int x = 0; x < samples.length; x++)
|
||||||
{
|
// {
|
||||||
Data<Number,Number> sample = mPreviousSamples.get(x);
|
// Data<Number,Number> sample = mPreviousSamples.get(x);
|
||||||
sample.setYValue(samples[x] ? -0.4f : -0.6f);
|
// sample.setYValue(samples[x] ? -0.4f : -0.6f);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Data<Number,Number> ideal1 = mIdeal.get(0);
|
// Data<Number,Number> ideal1 = mIdeal.get(0);
|
||||||
ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
// ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||||
Data<Number,Number> ideal2 = mIdeal.get(1);
|
// Data<Number,Number> ideal2 = mIdeal.get(1);
|
||||||
ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
// ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||||
|
//
|
||||||
if(error == 0.0f)
|
// if(error == 0.0f)
|
||||||
{
|
// {
|
||||||
Data<Number,Number> detected1 = mDetected.get(0);
|
// Data<Number,Number> detected1 = mDetected.get(0);
|
||||||
detected1.setXValue(0.0f);
|
// detected1.setXValue(0.0f);
|
||||||
Data<Number,Number> detected2 = mDetected.get(1);
|
// Data<Number,Number> detected2 = mDetected.get(1);
|
||||||
detected2.setXValue(0.0f);
|
// detected2.setXValue(0.0f);
|
||||||
}
|
// }
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
Data<Number,Number> detected1 = mDetected.get(0);
|
// Data<Number,Number> detected1 = mDetected.get(0);
|
||||||
detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
// detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||||
Data<Number,Number> detected2 = mDetected.get(1);
|
// Data<Number,Number> detected2 = mDetected.get(1);
|
||||||
detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
// detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdr-trunk
|
* *****************************************************************************
|
||||||
* 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
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* 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
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* 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.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.gui.instrument.decoder;
|
package io.github.dsheirer.gui.instrument.decoder;
|
||||||
|
|
||||||
import io.github.dsheirer.gui.instrument.chart.LTRNetSampleBufferChart;
|
import io.github.dsheirer.gui.instrument.chart.LTRNetSampleBufferChart;
|
||||||
@@ -142,8 +145,8 @@ public class LTRNetPane extends RealDecoderPane
|
|||||||
{
|
{
|
||||||
if(mZeroCrossingErrorDetectorChart == null)
|
if(mZeroCrossingErrorDetectorChart == null)
|
||||||
{
|
{
|
||||||
mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
|
// mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
|
||||||
getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
|
// getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mZeroCrossingErrorDetectorChart;
|
return mZeroCrossingErrorDetectorChart;
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.gui.playlist.alias;
|
package io.github.dsheirer.gui.playlist.alias;
|
||||||
@@ -37,6 +34,7 @@ import io.github.dsheirer.alias.action.script.ScriptAction;
|
|||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.AliasIDType;
|
import io.github.dsheirer.alias.id.AliasIDType;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
@@ -63,6 +61,11 @@ import io.github.dsheirer.preference.PreferenceType;
|
|||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
@@ -106,12 +109,6 @@ import org.controlsfx.control.ToggleSwitch;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor for configuring individual aliases
|
* Editor for configuring individual aliases
|
||||||
*/
|
*/
|
||||||
@@ -717,6 +714,7 @@ public class AliasItemEditor extends Editor<Alias>
|
|||||||
Menu nbfmMenu = new ProtocolMenu(Protocol.NBFM);
|
Menu nbfmMenu = new ProtocolMenu(Protocol.NBFM);
|
||||||
nbfmMenu.getItems().add(new AddTalkgroupItem(Protocol.NBFM));
|
nbfmMenu.getItems().add(new AddTalkgroupItem(Protocol.NBFM));
|
||||||
nbfmMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.NBFM));
|
nbfmMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.NBFM));
|
||||||
|
nbfmMenu.getItems().add(new AddDcsItem());
|
||||||
|
|
||||||
Menu passportMenu = new ProtocolMenu(Protocol.PASSPORT);
|
Menu passportMenu = new ProtocolMenu(Protocol.PASSPORT);
|
||||||
passportMenu.getItems().add(new AddTalkgroupItem(Protocol.PASSPORT));
|
passportMenu.getItems().add(new AddTalkgroupItem(Protocol.PASSPORT));
|
||||||
@@ -1281,6 +1279,24 @@ public class AliasItemEditor extends Editor<Alias>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Digital Coded Squelch (DCS) alias identifier menu item
|
||||||
|
*/
|
||||||
|
public class AddDcsItem extends MenuItem
|
||||||
|
{
|
||||||
|
public AddDcsItem()
|
||||||
|
{
|
||||||
|
super("Digital Coded Squelch (DCS)");
|
||||||
|
setOnAction(event -> {
|
||||||
|
Dcs dcs = new Dcs();
|
||||||
|
getIdentifiersList().getItems().add(dcs);
|
||||||
|
getIdentifiersList().getSelectionModel().select(dcs);
|
||||||
|
getIdentifiersList().scrollTo(dcs);
|
||||||
|
modifiedProperty().set(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Menu Item for adding a new protocol-specific Radio ID alias identifier
|
* Menu Item for adding a new protocol-specific Radio ID alias identifier
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.gui.playlist.alias.identifier;
|
||||||
|
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor for Digital Coded Squelch (DCS) alias identifiers
|
||||||
|
*/
|
||||||
|
public class DcsEditor extends IdentifierEditor<Dcs>
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(DcsEditor.class);
|
||||||
|
private ComboBox<DCSCode> mDCSCodeComboBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public DcsEditor()
|
||||||
|
{
|
||||||
|
GridPane gridPane = new GridPane();
|
||||||
|
gridPane.setHgap(5);
|
||||||
|
|
||||||
|
Label typeLabel = new Label("DCS Code");
|
||||||
|
GridPane.setConstraints(typeLabel, 0, 0);
|
||||||
|
gridPane.getChildren().add(typeLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getDCSCodeComboBox(), 1, 0);
|
||||||
|
gridPane.getChildren().add(getDCSCodeComboBox());
|
||||||
|
|
||||||
|
getChildren().add(gridPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setItem(Dcs item)
|
||||||
|
{
|
||||||
|
super.setItem(item);
|
||||||
|
if(item.isValid())
|
||||||
|
{
|
||||||
|
getDCSCodeComboBox().getSelectionModel().select(item.getDCSCode());
|
||||||
|
}
|
||||||
|
modifiedProperty().set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save()
|
||||||
|
{
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
//no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combo-box loaded with DCS codes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private ComboBox<DCSCode> getDCSCodeComboBox()
|
||||||
|
{
|
||||||
|
if(mDCSCodeComboBox == null)
|
||||||
|
{
|
||||||
|
mDCSCodeComboBox = new ComboBox<>();
|
||||||
|
mDCSCodeComboBox.getItems().addAll(DCSCode.STANDARD_CODES);
|
||||||
|
mDCSCodeComboBox.getItems().addAll(DCSCode.INVERTED_CODES);
|
||||||
|
mDCSCodeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
getItem().setDCSCode(getDCSCodeComboBox().getSelectionModel().getSelectedItem());
|
||||||
|
modifiedProperty().set(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDCSCodeComboBox;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -35,6 +35,8 @@ public class IdentifierEditorFactory
|
|||||||
{
|
{
|
||||||
switch(type)
|
switch(type)
|
||||||
{
|
{
|
||||||
|
case DCS:
|
||||||
|
return new DcsEditor();
|
||||||
case ESN:
|
case ESN:
|
||||||
return new EsnEditor();
|
return new EsnEditor();
|
||||||
case LOJACK:
|
case LOJACK:
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private ToggleSwitch mUseCompressedTalkgroupsToggle;
|
private ToggleSwitch mUseCompressedTalkgroupsToggle;
|
||||||
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||||
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
||||||
private IntegerTextField mLogicalSlotNumberField;
|
private IntegerTextField mLogicalChannelNumberField;
|
||||||
private FrequencyField mDownlinkFrequencyField;
|
private FrequencyField mDownlinkFrequencyField;
|
||||||
// private FrequencyField mUplinkFrequencyField;
|
// private FrequencyField mUplinkFrequencyField;
|
||||||
private Button mAddTimeslotFrequencyButton;
|
private Button mAddTimeslotFrequencyButton;
|
||||||
@@ -172,7 +172,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
|
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
|
||||||
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
|
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
|
||||||
|
|
||||||
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
|
Label timeslotTableLabel = new Label("Logical Channel Number (LCN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies. LSN = Logical Slot Number");
|
||||||
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
||||||
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
||||||
gridPane.getChildren().add(timeslotTableLabel);
|
gridPane.getChildren().add(timeslotTableLabel);
|
||||||
@@ -194,17 +194,13 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
editorBox.setAlignment(Pos.CENTER_LEFT);
|
editorBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
editorBox.setSpacing(5);
|
editorBox.setSpacing(5);
|
||||||
|
|
||||||
Label lsnLabel = new Label("LSN");
|
Label lcnLabel = new Label("LCN");
|
||||||
editorBox.getChildren().addAll(lsnLabel,getLogicalSlotNumberField());
|
editorBox.getChildren().addAll(lcnLabel, getLogicalChannelNumberField());
|
||||||
|
|
||||||
Label downlinkLabel = new Label("Frequency (MHz)");
|
Label downlinkLabel = new Label("Frequency (MHz)");
|
||||||
downlinkLabel.setPadding(new Insets(0,0,0,5));
|
downlinkLabel.setPadding(new Insets(0,0,0,5));
|
||||||
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
|
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
|
||||||
|
|
||||||
// Label uplinkLabel = new Label("Uplink");
|
|
||||||
// uplinkLabel.setPadding(new Insets(0,0,0,5));
|
|
||||||
// editorBox.getChildren().addAll(uplinkLabel,getUplinkFrequencyField());
|
|
||||||
|
|
||||||
GridPane.setConstraints(editorBox, 0, row, 4, 1);
|
GridPane.setConstraints(editorBox, 0, row, 4, 1);
|
||||||
gridPane.getChildren().add(editorBox);
|
gridPane.getChildren().add(editorBox);
|
||||||
|
|
||||||
@@ -287,20 +283,22 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
|
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
|
||||||
mTimeslotFrequencyTable.setPrefHeight(100.0);
|
mTimeslotFrequencyTable.setPrefHeight(100.0);
|
||||||
|
|
||||||
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LSN");
|
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LCN");
|
||||||
numberColumn.setPrefWidth(70);
|
numberColumn.setPrefWidth(75);
|
||||||
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
|
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
|
||||||
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
|
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
|
||||||
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
|
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
|
||||||
|
|
||||||
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
|
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
|
||||||
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
|
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
|
||||||
downlinkColumn.setPrefWidth(175);
|
downlinkColumn.setPrefWidth(150);
|
||||||
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
|
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
|
||||||
|
|
||||||
// TableColumn<TimeslotFrequency,Number> uplinkColumn = new TableColumn("Uplink (MHz)");
|
TableColumn<TimeslotFrequency,String> lsnColumn = new TableColumn("IDs (TS1/TS2)");
|
||||||
// uplinkColumn.setCellValueFactory(cellData -> cellData.getValue().getUplinkMHz());
|
lsnColumn.setPrefWidth(225);
|
||||||
// mTimeslotFrequencyTable.getColumns().addAll(uplinkColumn);
|
lsnColumn.setCellValueFactory(new PropertyValueFactory<>("logicalSlotNumbers"));
|
||||||
|
mTimeslotFrequencyTable.getColumns().addAll(lsnColumn);
|
||||||
|
|
||||||
|
|
||||||
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
|
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
|
||||||
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
|
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
|
||||||
@@ -317,20 +315,20 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
//Preserve the current modified flag state since setting values in the editor will change it.
|
//Preserve the current modified flag state since setting values in the editor will change it.
|
||||||
boolean modified = modifiedProperty().get();
|
boolean modified = modifiedProperty().get();
|
||||||
|
|
||||||
getLogicalSlotNumberField().setDisable(timeslot == null);
|
getLogicalChannelNumberField().setDisable(timeslot == null);
|
||||||
getDownlinkFrequencyField().setDisable(timeslot == null);
|
getDownlinkFrequencyField().setDisable(timeslot == null);
|
||||||
// getUplinkFrequencyField().setDisable(timeslot == null);
|
// getUplinkFrequencyField().setDisable(timeslot == null);
|
||||||
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
|
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
|
||||||
|
|
||||||
if(timeslot != null)
|
if(timeslot != null)
|
||||||
{
|
{
|
||||||
getLogicalSlotNumberField().set(timeslot.getNumber());
|
getLogicalChannelNumberField().set(timeslot.getNumber());
|
||||||
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
|
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
|
||||||
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
|
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
getLogicalSlotNumberField().set(0);
|
getLogicalChannelNumberField().set(0);
|
||||||
getDownlinkFrequencyField().set(0);
|
getDownlinkFrequencyField().set(0);
|
||||||
// getUplinkFrequencyField().set(0);
|
// getUplinkFrequencyField().set(0);
|
||||||
}
|
}
|
||||||
@@ -412,19 +410,19 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
return mDeleteTimeslotFrequencyButton;
|
return mDeleteTimeslotFrequencyButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntegerTextField getLogicalSlotNumberField()
|
private IntegerTextField getLogicalChannelNumberField()
|
||||||
{
|
{
|
||||||
if(mLogicalSlotNumberField == null)
|
if(mLogicalChannelNumberField == null)
|
||||||
{
|
{
|
||||||
mLogicalSlotNumberField = new IntegerTextField();
|
mLogicalChannelNumberField = new IntegerTextField();
|
||||||
mLogicalSlotNumberField.setDisable(true);
|
mLogicalChannelNumberField.setDisable(true);
|
||||||
mLogicalSlotNumberField.setPrefWidth(65);
|
mLogicalChannelNumberField.setPrefWidth(65);
|
||||||
mLogicalSlotNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
|
mLogicalChannelNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
if(selected != null)
|
if(selected != null)
|
||||||
{
|
{
|
||||||
Integer value = mLogicalSlotNumberField.get();
|
Integer value = mLogicalChannelNumberField.get();
|
||||||
|
|
||||||
if(value != null)
|
if(value != null)
|
||||||
{
|
{
|
||||||
@@ -436,7 +434,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return mLogicalSlotNumberField;
|
return mLogicalChannelNumberField;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FrequencyField getDownlinkFrequencyField()
|
private FrequencyField getDownlinkFrequencyField()
|
||||||
@@ -610,8 +608,8 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
getTimeslotTable().setDisable(config == null);
|
getTimeslotTable().setDisable(config == null);
|
||||||
getAddTimeslotFrequencyButton().setDisable(config == null);
|
getAddTimeslotFrequencyButton().setDisable(config == null);
|
||||||
getDeleteTimeslotFrequencyButton().setDisable(true);
|
getDeleteTimeslotFrequencyButton().setDisable(true);
|
||||||
getLogicalSlotNumberField().set(0);
|
getLogicalChannelNumberField().set(0);
|
||||||
getLogicalSlotNumberField().setDisable(true);
|
getLogicalChannelNumberField().setDisable(true);
|
||||||
getDownlinkFrequencyField().set(0);
|
getDownlinkFrequencyField().set(0);
|
||||||
getDownlinkFrequencyField().setDisable(true);
|
getDownlinkFrequencyField().setDisable(true);
|
||||||
// getUplinkFrequencyField().set(0);
|
// getUplinkFrequencyField().set(0);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
|
|||||||
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
|
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
|
||||||
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
|
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
|
||||||
import io.github.dsheirer.module.decode.DecoderType;
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
|
import io.github.dsheirer.module.decode.analog.DecodeConfigAnalog;
|
||||||
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
||||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
|
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
|
||||||
@@ -40,8 +41,6 @@ import io.github.dsheirer.record.RecorderType;
|
|||||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
@@ -58,15 +57,15 @@ import javafx.scene.layout.GridPane;
|
|||||||
import javafx.scene.text.TextAlignment;
|
import javafx.scene.text.TextAlignment;
|
||||||
import org.controlsfx.control.SegmentedButton;
|
import org.controlsfx.control.SegmentedButton;
|
||||||
import org.controlsfx.control.ToggleSwitch;
|
import org.controlsfx.control.ToggleSwitch;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Narrow-Band FM channel configuration editor
|
* Narrow-Band FM channel configuration editor
|
||||||
*/
|
*/
|
||||||
public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(NBFMConfigurationEditor.class);
|
|
||||||
private TitledPane mAuxDecoderPane;
|
private TitledPane mAuxDecoderPane;
|
||||||
private TitledPane mDecoderPane;
|
private TitledPane mDecoderPane;
|
||||||
private TitledPane mEventLogPane;
|
private TitledPane mEventLogPane;
|
||||||
@@ -75,8 +74,9 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private TextField mTalkgroupField;
|
private TextField mTalkgroupField;
|
||||||
private TextField mSquelchThresholdField;
|
private TextField mSquelchThresholdField;
|
||||||
private ToggleSwitch mSquelchAutoTrackSwitch;
|
private ToggleSwitch mSquelchAutoTrackSwitch;
|
||||||
|
private ToggleSwitch mAudioFilterEnable;
|
||||||
private TextFormatter<Integer> mTalkgroupTextFormatter;
|
private TextFormatter<Integer> mTalkgroupTextFormatter;
|
||||||
private IntegerFormatter mSquelchTextFormatter = new IntegerFormatter((int)DbPowerMeter.DEFAULT_MINIMUM_POWER,
|
private final IntegerFormatter mSquelchTextFormatter = new IntegerFormatter((int)DbPowerMeter.DEFAULT_MINIMUM_POWER,
|
||||||
(int)DbPowerMeter.DEFAULT_MAXIMUM_POWER);
|
(int)DbPowerMeter.DEFAULT_MAXIMUM_POWER);
|
||||||
private ToggleSwitch mBasebandRecordSwitch;
|
private ToggleSwitch mBasebandRecordSwitch;
|
||||||
private SegmentedButton mBandwidthButton;
|
private SegmentedButton mBandwidthButton;
|
||||||
@@ -84,9 +84,9 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private SourceConfigurationEditor mSourceConfigurationEditor;
|
private SourceConfigurationEditor mSourceConfigurationEditor;
|
||||||
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
|
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
|
||||||
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
||||||
private TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
|
private final TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
|
||||||
private IntegerFormatter mDecimalFormatter = new IntegerFormatter(1, 65535);
|
private final IntegerFormatter mDecimalFormatter = new IntegerFormatter(1, 65535);
|
||||||
private HexFormatter mHexFormatter = new HexFormatter(1, 65535);
|
private final HexFormatter mHexFormatter = new HexFormatter(1, 65535);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -162,12 +162,15 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
|
|
||||||
Label talkgroupLabel = new Label("Talkgroup To Assign");
|
Label talkgroupLabel = new Label("Talkgroup To Assign");
|
||||||
GridPane.setHalignment(talkgroupLabel, HPos.RIGHT);
|
GridPane.setHalignment(talkgroupLabel, HPos.RIGHT);
|
||||||
GridPane.setConstraints(talkgroupLabel, 6, 0);
|
GridPane.setConstraints(talkgroupLabel, 0, 1);
|
||||||
gridPane.getChildren().add(talkgroupLabel);
|
gridPane.getChildren().add(talkgroupLabel);
|
||||||
|
|
||||||
GridPane.setConstraints(getTalkgroupField(), 7, 0);
|
GridPane.setConstraints(getTalkgroupField(), 1, 1);
|
||||||
gridPane.getChildren().add(getTalkgroupField());
|
gridPane.getChildren().add(getTalkgroupField());
|
||||||
|
|
||||||
|
GridPane.setConstraints(getAudioFilterEnable(), 2, 1);
|
||||||
|
gridPane.getChildren().add(getAudioFilterEnable());
|
||||||
|
|
||||||
mDecoderPane.setContent(gridPane);
|
mDecoderPane.setContent(gridPane);
|
||||||
|
|
||||||
//Special handling - the pill button doesn't like to set a selected state if the pane is not expanded,
|
//Special handling - the pill button doesn't like to set a selected state if the pane is not expanded,
|
||||||
@@ -275,6 +278,22 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
return mAuxDecoderConfigurationEditor;
|
return mAuxDecoderConfigurationEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle switch for enable/disable the audio filtering in the audio module.
|
||||||
|
* @return toggle switch.
|
||||||
|
*/
|
||||||
|
private ToggleSwitch getAudioFilterEnable()
|
||||||
|
{
|
||||||
|
if(mAudioFilterEnable == null)
|
||||||
|
{
|
||||||
|
mAudioFilterEnable = new ToggleSwitch("High-Pass Audio Filter");
|
||||||
|
mAudioFilterEnable.setTooltip(new Tooltip("High-pass filter to remove DC offset and sub-audible signalling"));
|
||||||
|
mAudioFilterEnable.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAudioFilterEnable;
|
||||||
|
}
|
||||||
|
|
||||||
private SegmentedButton getBandwidthButton()
|
private SegmentedButton getBandwidthButton()
|
||||||
{
|
{
|
||||||
if(mBandwidthButton == null)
|
if(mBandwidthButton == null)
|
||||||
@@ -298,31 +317,27 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
//decode configuration and we're unable to correctly set the bandwidth setting. As a work
|
//decode configuration and we're unable to correctly set the bandwidth setting. As a work
|
||||||
//around, we'll listen for the toggles to be added and update them here. This normally only
|
//around, we'll listen for the toggles to be added and update them here. This normally only
|
||||||
//happens when we first instantiate the editor and load an item for editing the first time.
|
//happens when we first instantiate the editor and load an item for editing the first time.
|
||||||
mBandwidthButton.getToggleGroup().getToggles().addListener(new ListChangeListener<Toggle>()
|
mBandwidthButton.getToggleGroup().getToggles().addListener((ListChangeListener<Toggle>)c ->
|
||||||
{
|
{
|
||||||
@Override
|
//This change event happens when the toggles are added -- we don't need to inspect the change event
|
||||||
public void onChanged(Change<? extends Toggle> c)
|
if(getItem() != null && getItem().getDecodeConfiguration() instanceof DecodeConfigNBFM)
|
||||||
{
|
{
|
||||||
//This change event happens when the toggles are added -- we don't need to inspect the change event
|
//Capture current modified state so that we can reapply after adjusting control states
|
||||||
if(getItem() != null && getItem().getDecodeConfiguration() instanceof DecodeConfigNBFM)
|
boolean modified = modifiedProperty().get();
|
||||||
|
|
||||||
|
DecodeConfigNBFM config = (DecodeConfigNBFM)getItem().getDecodeConfiguration();
|
||||||
|
DecodeConfigNBFM.Bandwidth bandwidth = config.getBandwidth();
|
||||||
|
if(bandwidth == null)
|
||||||
{
|
{
|
||||||
//Capture current modified state so that we can reapply after adjusting control states
|
bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
|
||||||
boolean modified = modifiedProperty().get();
|
|
||||||
|
|
||||||
DecodeConfigNBFM config = (DecodeConfigNBFM)getItem().getDecodeConfiguration();
|
|
||||||
DecodeConfigNBFM.Bandwidth bandwidth = config.getBandwidth();
|
|
||||||
if(bandwidth == null)
|
|
||||||
{
|
|
||||||
bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(Toggle toggle: getBandwidthButton().getToggleGroup().getToggles())
|
|
||||||
{
|
|
||||||
toggle.setSelected(toggle.getUserData() == bandwidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiedProperty().set(modified);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for(Toggle toggle: getBandwidthButton().getToggleGroup().getToggles())
|
||||||
|
{
|
||||||
|
toggle.setSelected(toggle.getUserData() == bandwidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiedProperty().set(modified);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -449,6 +464,8 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
mSquelchTextFormatter.setValue(decodeConfigNBFM.getSquelchThreshold());
|
mSquelchTextFormatter.setValue(decodeConfigNBFM.getSquelchThreshold());
|
||||||
getSquelchAutoTrackSwitch().setDisable(false);
|
getSquelchAutoTrackSwitch().setDisable(false);
|
||||||
getSquelchAutoTrackSwitch().setSelected(decodeConfigNBFM.isSquelchAutoTrack());
|
getSquelchAutoTrackSwitch().setSelected(decodeConfigNBFM.isSquelchAutoTrack());
|
||||||
|
getAudioFilterEnable().setDisable(false);
|
||||||
|
getAudioFilterEnable().setSelected(decodeConfigNBFM.isAudioFilter());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -464,6 +481,8 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
getSquelchThresholdField().setDisable(true);
|
getSquelchThresholdField().setDisable(true);
|
||||||
getSquelchAutoTrackSwitch().setDisable(true);
|
getSquelchAutoTrackSwitch().setDisable(true);
|
||||||
getSquelchAutoTrackSwitch().setSelected(false);
|
getSquelchAutoTrackSwitch().setSelected(false);
|
||||||
|
getAudioFilterEnable().setDisable(true);
|
||||||
|
getAudioFilterEnable().setSelected(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,6 +519,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
config.setTalkgroup(talkgroup);
|
config.setTalkgroup(talkgroup);
|
||||||
config.setSquelchThreshold(mSquelchTextFormatter.getValue());
|
config.setSquelchThreshold(mSquelchTextFormatter.getValue());
|
||||||
config.setSquelchAutoTrack(getSquelchAutoTrackSwitch().isSelected());
|
config.setSquelchAutoTrack(getSquelchAutoTrackSwitch().isSelected());
|
||||||
|
config.setAudioFilter(getAudioFilterEnable().isSelected());
|
||||||
getItem().setDecodeConfiguration(config);
|
getItem().setDecodeConfiguration(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -41,13 +41,12 @@ import io.github.dsheirer.rrapi.type.Tag;
|
|||||||
import io.github.dsheirer.rrapi.type.Talkgroup;
|
import io.github.dsheirer.rrapi.type.Talkgroup;
|
||||||
import io.github.dsheirer.rrapi.type.Type;
|
import io.github.dsheirer.rrapi.type.Type;
|
||||||
import io.github.dsheirer.rrapi.type.Voice;
|
import io.github.dsheirer.rrapi.type.Voice;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for decoding type, flavor and voice for systems and formatting talkgroups according to user preferences.
|
* Utility class for decoding type, flavor and voice for systems and formatting talkgroups according to user preferences.
|
||||||
@@ -374,16 +373,10 @@ public class RadioReferenceDecoder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int lsn = (lcn - 1) * 2 + 1;
|
TimeslotFrequency timeslotFrequency = new TimeslotFrequency();
|
||||||
TimeslotFrequency timeslot1Frequency = new TimeslotFrequency();
|
timeslotFrequency.setNumber(lcn);
|
||||||
timeslot1Frequency.setNumber(lsn);
|
timeslotFrequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
||||||
timeslot1Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
frequencies.add(timeslotFrequency);
|
||||||
frequencies.add(timeslot1Frequency);
|
|
||||||
|
|
||||||
TimeslotFrequency timeslot2Frequency = new TimeslotFrequency();
|
|
||||||
timeslot2Frequency.setNumber(lsn + 1);
|
|
||||||
timeslot2Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
|
||||||
frequencies.add(timeslot2Frequency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return frequencies;
|
return frequencies;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,21 +20,16 @@
|
|||||||
package io.github.dsheirer.gui.playlist.streaming;
|
package io.github.dsheirer.gui.playlist.streaming;
|
||||||
|
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster;
|
|
||||||
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
|
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
|
||||||
import io.github.dsheirer.gui.control.IntegerTextField;
|
import io.github.dsheirer.gui.control.IntegerTextField;
|
||||||
import io.github.dsheirer.playlist.PlaylistManager;
|
import io.github.dsheirer.playlist.PlaylistManager;
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.controlsfx.control.ToggleSwitch;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RdioScanner calls API configuration editor
|
* RdioScanner calls API configuration editor
|
||||||
@@ -42,6 +37,7 @@ import org.controlsfx.control.ToggleSwitch;
|
|||||||
public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfiguration>
|
public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfiguration>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerEditor.class);
|
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerEditor.class);
|
||||||
|
private static final String API_PATH = "/api/call-upload";
|
||||||
private IntegerTextField mSystemIdTextField;
|
private IntegerTextField mSystemIdTextField;
|
||||||
private IntegerTextField mMaxAgeTextField;
|
private IntegerTextField mMaxAgeTextField;
|
||||||
private TextField mApiKeyTextField;
|
private TextField mApiKeyTextField;
|
||||||
@@ -71,7 +67,14 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
|
|||||||
{
|
{
|
||||||
getSystemIdTextField().set(item.getSystemID());
|
getSystemIdTextField().set(item.getSystemID());
|
||||||
getApiKeyTextField().setText(item.getApiKey());
|
getApiKeyTextField().setText(item.getApiKey());
|
||||||
getHostTextField().setText(item.getHost());
|
String url = item.getHost();
|
||||||
|
|
||||||
|
if(url != null)
|
||||||
|
{
|
||||||
|
url = url.replace(API_PATH, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
getHostTextField().setText(url);
|
||||||
getMaxAgeTextField().set((int)(item.getMaximumRecordingAge() / 1000));
|
getMaxAgeTextField().set((int)(item.getMaximumRecordingAge() / 1000));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -97,7 +100,13 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
|
|||||||
{
|
{
|
||||||
int systemID = getSystemIdTextField().get() != null ? getSystemIdTextField().get() : 0;
|
int systemID = getSystemIdTextField().get() != null ? getSystemIdTextField().get() : 0;
|
||||||
getItem().setSystemID(systemID);
|
getItem().setSystemID(systemID);
|
||||||
getItem().setHost(getHostTextField().getText());
|
String host = getHostTextField().getText();
|
||||||
|
if(host != null)
|
||||||
|
{
|
||||||
|
host = host.replace(API_PATH, "");
|
||||||
|
host += API_PATH;
|
||||||
|
getItem().setHost(host);
|
||||||
|
}
|
||||||
getItem().setApiKey(getApiKeyTextField().getText());
|
getItem().setApiKey(getApiKeyTextField().getText());
|
||||||
getItem().setMaximumRecordingAge(getMaxAgeTextField().get() * 1000);
|
getItem().setMaximumRecordingAge(getMaxAgeTextField().get() * 1000);
|
||||||
}
|
}
|
||||||
@@ -169,6 +178,11 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
|
|||||||
GridPane.setConstraints(getHostTextField(), 1, row);
|
GridPane.setConstraints(getHostTextField(), 1, row);
|
||||||
mEditorPane.getChildren().add(getHostTextField());
|
mEditorPane.getChildren().add(getHostTextField());
|
||||||
|
|
||||||
|
Label apiPath = new Label("/api/call-upload");
|
||||||
|
GridPane.setHalignment(apiPath, HPos.LEFT);
|
||||||
|
GridPane.setConstraints(apiPath, 2, row);
|
||||||
|
mEditorPane.getChildren().add(apiPath);
|
||||||
|
|
||||||
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
|
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
|
||||||
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
|
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
|
||||||
GridPane.setConstraints(maxAgeLabel, 0, ++row);
|
GridPane.setConstraints(maxAgeLabel, 0, ++row);
|
||||||
|
|||||||
@@ -17,20 +17,22 @@
|
|||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.gui.dmr;
|
package io.github.dsheirer.gui.viewer;
|
||||||
|
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRMessageFramer;
|
import io.github.dsheirer.module.decode.dmr.DMRMessageFramer;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor;
|
import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor;
|
||||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||||
import io.github.dsheirer.record.binary.BinaryReader;
|
import io.github.dsheirer.record.binary.BinaryReader;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
@@ -40,10 +42,10 @@ import javafx.collections.transformation.FilteredList;
|
|||||||
import javafx.collections.transformation.SortedList;
|
import javafx.collections.transformation.SortedList;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TablePosition;
|
import javafx.scene.control.TablePosition;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
@@ -58,21 +60,19 @@ import javafx.scene.layout.HBox;
|
|||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility application to load and view a DMR .bits recording file with the messages fully parsed.
|
* DMR Viewer panel
|
||||||
*/
|
*/
|
||||||
public class DMRRecordingViewer extends VBox
|
public class DmrViewer extends VBox
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(DMRRecordingViewer.class);
|
private static final Logger mLog = LoggerFactory.getLogger(DmrViewer.class);
|
||||||
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
|
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
|
||||||
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory";
|
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.dmr";
|
||||||
private Preferences mPreferences = Preferences.userNodeForPackage(DMRRecordingViewer.class);
|
private Preferences mPreferences = Preferences.userNodeForPackage(DmrViewer.class);
|
||||||
private Scene mScene;
|
|
||||||
private Button mSelectFileButton;
|
private Button mSelectFileButton;
|
||||||
private Label mSelectedFileLabel;
|
private Label mSelectedFileLabel;
|
||||||
private TableView<IMessage> mMessageTableView;
|
private TableView<IMessage> mMessageTableView;
|
||||||
@@ -86,8 +86,9 @@ public class DMRRecordingViewer extends VBox
|
|||||||
private TextField mFindText;
|
private TextField mFindText;
|
||||||
private Button mFindButton;
|
private Button mFindButton;
|
||||||
private Button mFindNextButton;
|
private Button mFindNextButton;
|
||||||
|
private ProgressIndicator mLoadingIndicator;
|
||||||
|
|
||||||
public DMRRecordingViewer()
|
public DmrViewer()
|
||||||
{
|
{
|
||||||
setPadding(new Insets(5));
|
setPadding(new Insets(5));
|
||||||
setSpacing(5);
|
setSpacing(5);
|
||||||
@@ -132,6 +133,21 @@ public class DMRRecordingViewer extends VBox
|
|||||||
getChildren().addAll(fileBox, filterBox, getMessageTableView());
|
getChildren().addAll(fileBox, filterBox, getMessageTableView());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spinny loading icon to show over the message table view
|
||||||
|
*/
|
||||||
|
private ProgressIndicator getLoadingIndicator()
|
||||||
|
{
|
||||||
|
if(mLoadingIndicator == null)
|
||||||
|
{
|
||||||
|
mLoadingIndicator = new ProgressIndicator();
|
||||||
|
mLoadingIndicator.setProgress(-1);
|
||||||
|
mLoadingIndicator.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLoadingIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the recording file and loads the content into the viewer
|
* Processes the recording file and loads the content into the viewer
|
||||||
* @param file containing a .bits recording of decoded DMR data.
|
* @param file containing a .bits recording of decoded DMR data.
|
||||||
@@ -140,33 +156,45 @@ public class DMRRecordingViewer extends VBox
|
|||||||
{
|
{
|
||||||
if(file != null && file.exists())
|
if(file != null && file.exists())
|
||||||
{
|
{
|
||||||
getSelectedFileLabel().setText(file.getName());
|
|
||||||
mMessages.clear();
|
mMessages.clear();
|
||||||
|
getLoadingIndicator().setVisible(true);
|
||||||
|
getSelectedFileLabel().setText("Loading ...");
|
||||||
|
final boolean useCompressed = getUseCompressedTalkgroups().isSelected();
|
||||||
|
|
||||||
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
|
ThreadPool.CACHED.submit(new Runnable()
|
||||||
DecodeConfigDMR config = new DecodeConfigDMR();
|
|
||||||
if(getUseCompressedTalkgroups().isSelected())
|
|
||||||
{
|
{
|
||||||
config.setUseCompressedTalkgroups(true);
|
@Override
|
||||||
}
|
public void run()
|
||||||
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();
|
List<IMessage> messages = new ArrayList<>();
|
||||||
messageFramer.receive(buffer);
|
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
|
||||||
}
|
DecodeConfigDMR config = new DecodeConfigDMR();
|
||||||
}
|
config.setUseCompressedTalkgroups(useCompressed);
|
||||||
catch(Exception ioe)
|
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
|
||||||
{
|
messageFramer.setListener(messageProcessor);
|
||||||
ioe.printStackTrace();
|
messageProcessor.setMessageListener(message -> messages.add(message));
|
||||||
}
|
|
||||||
|
|
||||||
getMessageTableView().scrollTo(0);
|
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
|
||||||
|
{
|
||||||
|
while(reader.hasNext())
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = reader.next();
|
||||||
|
messageFramer.receive(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception ioe)
|
||||||
|
{
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
getLoadingIndicator().setVisible(false);
|
||||||
|
getSelectedFileLabel().setText(file.getName());
|
||||||
|
mMessages.addAll(messages);
|
||||||
|
getMessageTableView().scrollTo(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,9 +204,9 @@ public class DMRRecordingViewer extends VBox
|
|||||||
private void updateFilters()
|
private void updateFilters()
|
||||||
{
|
{
|
||||||
Predicate<IMessage> timeslotPredicate = message ->
|
Predicate<IMessage> timeslotPredicate = message ->
|
||||||
(getShowTS0().isSelected() && (message.getTimeslot() == 0)) ||
|
(getShowTS0().isSelected() && (message.getTimeslot() == 0)) ||
|
||||||
(getShowTS1().isSelected() && (message.getTimeslot() == 1)) ||
|
(getShowTS1().isSelected() && (message.getTimeslot() == 1)) ||
|
||||||
(getShowTS2().isSelected() && (message.getTimeslot() == 2));
|
(getShowTS2().isSelected() && (message.getTimeslot() == 2));
|
||||||
|
|
||||||
String filterText = getSearchText().getText();
|
String filterText = getSearchText().getText();
|
||||||
|
|
||||||
@@ -256,6 +284,7 @@ public class DMRRecordingViewer extends VBox
|
|||||||
if(mMessageTableView == null)
|
if(mMessageTableView == null)
|
||||||
{
|
{
|
||||||
mMessageTableView = new TableView<>();
|
mMessageTableView = new TableView<>();
|
||||||
|
mMessageTableView.setPlaceholder(getLoadingIndicator());
|
||||||
SortedList<IMessage> sortedList = new SortedList<>(mFilteredMessages);
|
SortedList<IMessage> sortedList = new SortedList<>(mFilteredMessages);
|
||||||
sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty());
|
sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty());
|
||||||
mMessageTableView.setItems(sortedList);
|
mMessageTableView.setItems(sortedList);
|
||||||
@@ -459,6 +488,12 @@ public class DMRRecordingViewer extends VBox
|
|||||||
if(mFindText == null)
|
if(mFindText == null)
|
||||||
{
|
{
|
||||||
mFindText = new TextField();
|
mFindText = new TextField();
|
||||||
|
mFindText.setOnKeyPressed(event -> {
|
||||||
|
if(event.getCode().equals(KeyCode.ENTER))
|
||||||
|
{
|
||||||
|
getFindButton().fire();
|
||||||
|
}
|
||||||
|
});
|
||||||
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,37 +538,9 @@ public class DMRRecordingViewer extends VBox
|
|||||||
{
|
{
|
||||||
if(mUseCompressedTalkgroups == null)
|
if(mUseCompressedTalkgroups == null)
|
||||||
{
|
{
|
||||||
mUseCompressedTalkgroups = new CheckBox("Use Compressed Talkgroups");
|
mUseCompressedTalkgroups = new CheckBox("Use Hytera Tier III Compressed Talkgroups");
|
||||||
}
|
}
|
||||||
|
|
||||||
return mUseCompressedTalkgroups;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
462
src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java
Normal file
462
src/main/java/io/github/dsheirer/gui/viewer/P25P1Viewer.java
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.viewer;
|
||||||
|
|
||||||
|
import io.github.dsheirer.message.IMessage;
|
||||||
|
import io.github.dsheirer.message.StuffBitsMessage;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageFramer;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1MessageProcessor;
|
||||||
|
import io.github.dsheirer.record.binary.BinaryReader;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
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.collections.transformation.SortedList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TablePosition;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APCO25 Phase 1 viewer panel
|
||||||
|
*/
|
||||||
|
public class P25P1Viewer extends VBox
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(P25P1Viewer.class);
|
||||||
|
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
|
||||||
|
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory.p25p1";
|
||||||
|
private Preferences mPreferences = Preferences.userNodeForPackage(P25P1Viewer.class);
|
||||||
|
private Button mSelectFileButton;
|
||||||
|
private Label mSelectedFileLabel;
|
||||||
|
private TableView<IMessage> mMessageTableView;
|
||||||
|
private ObservableList<IMessage> mMessages = FXCollections.observableArrayList();
|
||||||
|
private FilteredList<IMessage> mFilteredMessages = new FilteredList<>(mMessages);
|
||||||
|
private TextField mSearchText;
|
||||||
|
private TextField mFindText;
|
||||||
|
private Button mFindButton;
|
||||||
|
private Button mFindNextButton;
|
||||||
|
private ProgressIndicator mLoadingIndicator;
|
||||||
|
|
||||||
|
public P25P1Viewer()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
fileBox.getChildren().addAll(getSelectFileButton(), getSelectedFileLabel());
|
||||||
|
|
||||||
|
HBox filterBox = new HBox();
|
||||||
|
filterBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
filterBox.setAlignment(Pos.BASELINE_CENTER);
|
||||||
|
filterBox.setSpacing(5);
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* Note: invoke this method off of the UI thread in a thread pool executor and the results will be loaded into the
|
||||||
|
* message table back on the JavaFX UI thread.
|
||||||
|
*
|
||||||
|
* @param file containing a .bits recording of decoded DMR data.
|
||||||
|
*/
|
||||||
|
private void load(File file)
|
||||||
|
{
|
||||||
|
if(file != null && file.exists())
|
||||||
|
{
|
||||||
|
mMessages.clear();
|
||||||
|
getLoadingIndicator().setVisible(true);
|
||||||
|
getSelectedFileLabel().setText("Loading ...");
|
||||||
|
|
||||||
|
ThreadPool.CACHED.submit(() -> {
|
||||||
|
List<IMessage> messages = new ArrayList<>();
|
||||||
|
P25P1MessageFramer messageFramer = new P25P1MessageFramer(null, 9600);
|
||||||
|
P25P1MessageProcessor messageProcessor = new P25P1MessageProcessor();
|
||||||
|
messageFramer.setListener(messageProcessor);
|
||||||
|
messageProcessor.setMessageListener(message -> {
|
||||||
|
if(!(message instanceof StuffBitsMessage))
|
||||||
|
{
|
||||||
|
messages.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
getLoadingIndicator().setVisible(false);
|
||||||
|
getSelectedFileLabel().setText(file.getName());
|
||||||
|
mMessages.addAll(messages);
|
||||||
|
getMessageTableView().scrollTo(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the filter(s) applies to the list of messages
|
||||||
|
*/
|
||||||
|
private void updateFilters()
|
||||||
|
{
|
||||||
|
String filterText = getSearchText().getText();
|
||||||
|
|
||||||
|
if(filterText != null && !filterText.isEmpty())
|
||||||
|
{
|
||||||
|
Predicate<IMessage> textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase());
|
||||||
|
mFilteredMessages.setPredicate(textPredicate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mFilteredMessages.setPredicate(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<>();
|
||||||
|
mMessageTableView.setPlaceholder(getLoadingIndicator());
|
||||||
|
SortedList<IMessage> sortedList = new SortedList<>(mFilteredMessages);
|
||||||
|
sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty());
|
||||||
|
mMessageTableView.setItems(sortedList);
|
||||||
|
|
||||||
|
mMessageTableView.setOnKeyPressed(event ->
|
||||||
|
{
|
||||||
|
if(KEY_CODE_COPY.match(event))
|
||||||
|
{
|
||||||
|
final Set<Integer> rows = new TreeSet<>();
|
||||||
|
for (final TablePosition tablePosition : mMessageTableView.getSelectionModel().getSelectedCells())
|
||||||
|
{
|
||||||
|
rows.add(tablePosition.getRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
boolean firstRow = true;
|
||||||
|
for (final Integer row : rows)
|
||||||
|
{
|
||||||
|
if(firstRow)
|
||||||
|
{
|
||||||
|
firstRow = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean firstCol = true;
|
||||||
|
|
||||||
|
for (final TableColumn<?, ?> column : mMessageTableView.getColumns())
|
||||||
|
{
|
||||||
|
if(firstCol)
|
||||||
|
{
|
||||||
|
firstCol = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append('\t');
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object cellData = column.getCellData(row);
|
||||||
|
sb.append(cellData == null ? "" : cellData.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ClipboardContent clipboardContent = new ClipboardContent();
|
||||||
|
clipboardContent.putString(sb.toString());
|
||||||
|
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TableColumn timestampColumn = new TableColumn();
|
||||||
|
timestampColumn.setPrefWidth(110);
|
||||||
|
timestampColumn.setText("Time");
|
||||||
|
timestampColumn.setCellValueFactory(new PropertyValueFactory<>("timestamp"));
|
||||||
|
|
||||||
|
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, 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"));
|
||||||
|
final File selected = fileChooser.showOpenDialog(getScene().getWindow());
|
||||||
|
|
||||||
|
if(selected != null)
|
||||||
|
{
|
||||||
|
mPreferences.put(LAST_SELECTED_DIRECTORY, selected.getParent());
|
||||||
|
load(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSelectFileButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spinny loading icon to show over the message table view
|
||||||
|
*/
|
||||||
|
private ProgressIndicator getLoadingIndicator()
|
||||||
|
{
|
||||||
|
if(mLoadingIndicator == null)
|
||||||
|
{
|
||||||
|
mLoadingIndicator = new ProgressIndicator();
|
||||||
|
mLoadingIndicator.setProgress(-1);
|
||||||
|
mLoadingIndicator.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mLoadingIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected file path label.
|
||||||
|
*/
|
||||||
|
private Label getSelectedFileLabel()
|
||||||
|
{
|
||||||
|
if(mSelectedFileLabel == null)
|
||||||
|
{
|
||||||
|
mSelectedFileLabel = new Label(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSelectedFileLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.setOnKeyPressed(event -> {
|
||||||
|
if(event.getCode().equals(KeyCode.ENTER))
|
||||||
|
{
|
||||||
|
getFindButton().fire();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
180
src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java
Normal file
180
src/main/java/io/github/dsheirer/gui/viewer/RecordingViewer.java
Normal 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.gui.viewer;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Menu;
|
||||||
|
import javafx.scene.control.MenuBar;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.control.SeparatorMenuItem;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.control.TabPane;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility application to load and view .bits recording file with the messages fully parsed.
|
||||||
|
*
|
||||||
|
* Supported Protocols: DMR and APCO25 Phase 1.
|
||||||
|
*/
|
||||||
|
public class RecordingViewer extends VBox
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(RecordingViewer.class);
|
||||||
|
private MenuBar mMenuBar;
|
||||||
|
private TabPane mTabPane;
|
||||||
|
private int mTabCounterDmr = 1;
|
||||||
|
private int mTabCounterP25P1 = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public RecordingViewer()
|
||||||
|
{
|
||||||
|
VBox.setVgrow(getTabPane(), Priority.ALWAYS);
|
||||||
|
getChildren().addAll(getMenuBar(), getTabPane());
|
||||||
|
}
|
||||||
|
|
||||||
|
public MenuBar getMenuBar()
|
||||||
|
{
|
||||||
|
if(mMenuBar == null)
|
||||||
|
{
|
||||||
|
mMenuBar = new MenuBar();
|
||||||
|
Menu fileMenu = new Menu("File");
|
||||||
|
|
||||||
|
Menu createNewViewerMenu = new Menu("New Viewer ...");
|
||||||
|
MenuItem dmrMenuItem = new MenuItem("DMR");
|
||||||
|
dmrMenuItem.onActionProperty().set(event -> getTabPane().getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer())));
|
||||||
|
MenuItem p25p1MenuItem = new MenuItem("P25P1");
|
||||||
|
p25p1MenuItem.onActionProperty().set(event -> getTabPane().getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer())));
|
||||||
|
createNewViewerMenu.getItems().addAll(dmrMenuItem, p25p1MenuItem);
|
||||||
|
|
||||||
|
MenuItem exitMenu = new MenuItem("Exit");
|
||||||
|
exitMenu.onActionProperty().set(event -> ((Stage)getScene().getWindow()).close());
|
||||||
|
fileMenu.getItems().addAll(createNewViewerMenu, new SeparatorMenuItem(), exitMenu);
|
||||||
|
mMenuBar.getMenus().add(fileMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMenuBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab pane for each viewer instance
|
||||||
|
*/
|
||||||
|
public TabPane getTabPane()
|
||||||
|
{
|
||||||
|
if(mTabPane == null)
|
||||||
|
{
|
||||||
|
mTabPane = new TabPane();
|
||||||
|
mTabPane.setMaxHeight(Double.MAX_VALUE);
|
||||||
|
mTabPane.getTabs().add(new LabeledTab("DMR-" + mTabCounterDmr++, new DmrViewer()));
|
||||||
|
mTabPane.getTabs().add(new LabeledTab("P25P1-" + mTabCounterP25P1++, new P25P1Viewer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTabPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorates the tab with label renaming feature using double-click or right-click to change text
|
||||||
|
*/
|
||||||
|
public class LabeledTab extends Tab
|
||||||
|
{
|
||||||
|
private Label mLabel = new Label();
|
||||||
|
private TextField mTextField = new TextField();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param label to use initially
|
||||||
|
* @param node for content
|
||||||
|
*/
|
||||||
|
public LabeledTab(String label, Node node)
|
||||||
|
{
|
||||||
|
super(null, node);
|
||||||
|
|
||||||
|
mLabel.setText(label);
|
||||||
|
setGraphic(mLabel);
|
||||||
|
|
||||||
|
mLabel.setOnMouseClicked(event -> {
|
||||||
|
if((event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() > 1) ||
|
||||||
|
event.getButton().equals(MouseButton.SECONDARY))
|
||||||
|
{
|
||||||
|
setGraphic(mTextField);
|
||||||
|
mTextField.setText(mLabel.getText());
|
||||||
|
mTextField.selectAll();
|
||||||
|
mTextField.requestFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mTextField.setOnAction(event -> {
|
||||||
|
mLabel.setText(mTextField.getText());
|
||||||
|
LabeledTab.this.setGraphic(mLabel);
|
||||||
|
});
|
||||||
|
|
||||||
|
mTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
if(!newValue)
|
||||||
|
{
|
||||||
|
mLabel.setText(mTextField.getText());
|
||||||
|
LabeledTab.this.setGraphic(mLabel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LabeledTab(String label)
|
||||||
|
{
|
||||||
|
this(label, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
Application viewer = new Application()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) throws Exception
|
||||||
|
{
|
||||||
|
Scene scene = new Scene(new RecordingViewer(), 1100, 800);
|
||||||
|
primaryStage.setTitle("Message Recording Viewer (.bits)");
|
||||||
|
primaryStage.setScene(scene);
|
||||||
|
primaryStage.show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Runnable r = () -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
viewer.start(new Stage());
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error starting message recording viewer application", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Platform.startup(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,17 +17,17 @@
|
|||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.gui.dmr;
|
package io.github.dsheirer.gui.viewer;
|
||||||
|
|
||||||
import io.github.dsheirer.gui.JavaFxWindowRequest;
|
import io.github.dsheirer.gui.JavaFxWindowRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to show the DMR recording viewer
|
* Request to show the recording viewer
|
||||||
*/
|
*/
|
||||||
public class ViewDmrRecordingViewerRequest extends JavaFxWindowRequest
|
public class ViewRecordingViewerRequest extends JavaFxWindowRequest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
*/
|
*/
|
||||||
public ViewDmrRecordingViewerRequest() {}
|
public ViewRecordingViewerRequest() {}
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,7 @@ public enum Form
|
|||||||
CHANNEL_DESCRIPTOR,
|
CHANNEL_DESCRIPTOR,
|
||||||
CHANNEL_NAME,
|
CHANNEL_NAME,
|
||||||
CHANNEL_FREQUENCY,
|
CHANNEL_FREQUENCY,
|
||||||
|
DCS,
|
||||||
DECODER_TYPE,
|
DECODER_TYPE,
|
||||||
DTMF,
|
DTMF,
|
||||||
ENCRYPTION_KEY,
|
ENCRYPTION_KEY,
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.dcs;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Form;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digital Coded Squelch code identifier. This is decoded from the transmitted signal and represents the FROM role.
|
||||||
|
*/
|
||||||
|
public class DCSIdentifier extends Identifier<DCSCode>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param code detected.
|
||||||
|
*/
|
||||||
|
public DCSIdentifier(DCSCode code)
|
||||||
|
{
|
||||||
|
super(code, IdentifierClass.USER, Form.TONE, Role.FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Protocol getProtocol()
|
||||||
|
{
|
||||||
|
return Protocol.DCS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,69 +1,161 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.identifier.patch;
|
package io.github.dsheirer.identifier.patch;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary grouping or regrouping of talkgroups or individual radios.
|
||||||
|
*/
|
||||||
public class PatchGroup
|
public class PatchGroup
|
||||||
{
|
{
|
||||||
// private final static Logger mLog = LoggerFactory.getLogger(PatchGroup.class);
|
|
||||||
|
|
||||||
private TalkgroupIdentifier mPatchGroupIdentifier;
|
private TalkgroupIdentifier mPatchGroupIdentifier;
|
||||||
private List<TalkgroupIdentifier> mPatchedGroupIdentifiers = new ArrayList<>();
|
private List<TalkgroupIdentifier> mPatchedTalkgroupIdentifiers = new ArrayList<>();
|
||||||
|
private List<RadioIdentifier> mPatchedRadioIdentifiers = new ArrayList<>();
|
||||||
|
private int mVersion;
|
||||||
|
|
||||||
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier)
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param patchGroupIdentifier for the patch group
|
||||||
|
* @param version for this patch group identifier. Note: if the patch group identifier is frequently reused, a
|
||||||
|
* new version number will indicate a new instance of the patch group.
|
||||||
|
*/
|
||||||
|
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier, int version)
|
||||||
{
|
{
|
||||||
mPatchGroupIdentifier = patchGroupIdentifier;
|
mPatchGroupIdentifier = patchGroupIdentifier;
|
||||||
|
mVersion = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param patchGroupIdentifier that is the overall identifier for this temporary grouping.
|
||||||
|
*/
|
||||||
|
public PatchGroup(TalkgroupIdentifier patchGroupIdentifier)
|
||||||
|
{
|
||||||
|
this(patchGroupIdentifier, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version number for this patch group used to differentiate frequently reused patch group identifiers.
|
||||||
|
* @return version number (aka super group sequence number).
|
||||||
|
*/
|
||||||
|
public int getVersion()
|
||||||
|
{
|
||||||
|
return mVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch group identifier.
|
||||||
|
* @return identifier
|
||||||
|
*/
|
||||||
public TalkgroupIdentifier getPatchGroup()
|
public TalkgroupIdentifier getPatchGroup()
|
||||||
{
|
{
|
||||||
return mPatchGroupIdentifier;
|
return mPatchGroupIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPatchedGroup(TalkgroupIdentifier patchedGroupIdentifier)
|
/**
|
||||||
|
* Indicates if this patch group contains any patched talkgroups.
|
||||||
|
* @return true if there are patched talkgroups.
|
||||||
|
*/
|
||||||
|
public boolean hasPatchedTalkgroups()
|
||||||
{
|
{
|
||||||
if(!mPatchedGroupIdentifiers.contains(patchedGroupIdentifier))
|
return !getPatchedTalkgroupIdentifiers().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this patch group contains any patched radio identifiers.
|
||||||
|
* @return true if there are patched radios.
|
||||||
|
*/
|
||||||
|
public boolean hasPatchedRadios()
|
||||||
|
{
|
||||||
|
return !getPatchedRadioIdentifiers().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the talkgroup identifier to this patched group.
|
||||||
|
* @param patchedGroupIdentifier to add
|
||||||
|
*/
|
||||||
|
public void addPatchedTalkgroup(TalkgroupIdentifier patchedGroupIdentifier)
|
||||||
|
{
|
||||||
|
if(!mPatchedTalkgroupIdentifiers.contains(patchedGroupIdentifier))
|
||||||
{
|
{
|
||||||
mPatchedGroupIdentifiers.add(patchedGroupIdentifier);
|
mPatchedTalkgroupIdentifiers.add(patchedGroupIdentifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPatchedGroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
|
/**
|
||||||
|
* Adds a list of talkgroup identifiers to this patched group.
|
||||||
|
* @param patchedGroupIdentifiers to add
|
||||||
|
*/
|
||||||
|
public void addPatchedTalkgroups(List<TalkgroupIdentifier> patchedGroupIdentifiers)
|
||||||
{
|
{
|
||||||
for(TalkgroupIdentifier identifier : patchedGroupIdentifiers)
|
for(TalkgroupIdentifier identifier : patchedGroupIdentifiers)
|
||||||
{
|
{
|
||||||
addPatchedGroup(identifier);
|
addPatchedTalkgroup(identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TalkgroupIdentifier> getPatchedGroupIdentifiers()
|
/**
|
||||||
|
* List of patched group identifiers that are part of this patch group.
|
||||||
|
* @return patched talkgroups.
|
||||||
|
*/
|
||||||
|
public List<TalkgroupIdentifier> getPatchedTalkgroupIdentifiers()
|
||||||
{
|
{
|
||||||
return mPatchedGroupIdentifiers;
|
return mPatchedTalkgroupIdentifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the radio identifier to this patched group.
|
||||||
|
* @param patchedRadioIdentifier to add
|
||||||
|
*/
|
||||||
|
public void addPatchedRadio(RadioIdentifier patchedRadioIdentifier)
|
||||||
|
{
|
||||||
|
if(!mPatchedRadioIdentifiers.contains(patchedRadioIdentifier))
|
||||||
|
{
|
||||||
|
mPatchedRadioIdentifiers.add(patchedRadioIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a list of radio identifiers to this patched group.
|
||||||
|
* @param patchedRadioIdentifiers to add
|
||||||
|
*/
|
||||||
|
public void addPatchedRadios(List<RadioIdentifier> patchedRadioIdentifiers)
|
||||||
|
{
|
||||||
|
for(RadioIdentifier identifier : patchedRadioIdentifiers)
|
||||||
|
{
|
||||||
|
addPatchedRadio(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of patched radio identifiers that are part of this patch group.
|
||||||
|
* @return patched radios.
|
||||||
|
*/
|
||||||
|
public List<RadioIdentifier> getPatchedRadioIdentifiers()
|
||||||
|
{
|
||||||
|
return mPatchedRadioIdentifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -72,9 +164,14 @@ public class PatchGroup
|
|||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("P:").append(getPatchGroup());
|
sb.append("P:").append(getPatchGroup());
|
||||||
|
|
||||||
if(!getPatchedGroupIdentifiers().isEmpty())
|
if(hasPatchedTalkgroups())
|
||||||
{
|
{
|
||||||
sb.append(" ").append(getPatchedGroupIdentifiers());
|
sb.append(getPatchedTalkgroupIdentifiers());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasPatchedRadios())
|
||||||
|
{
|
||||||
|
sb.append(getPatchedRadioIdentifiers());
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,27 +22,23 @@ package io.github.dsheirer.identifier.patch;
|
|||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
import io.github.dsheirer.identifier.Role;
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager for (temporary) patch groups and related identifiers.
|
* Manager for (temporary) patch groups aka super groups. This manager monitors patch group additions and deletions and
|
||||||
|
* maintains a map of the current state of each patch group active in the system.
|
||||||
*
|
*
|
||||||
* Patch groups are primarily used on Motorola P25 systems. This manager monitors patch group additions and deletions
|
* Traffic channel calls will reference a patch group by simply using the patch group identifier. This
|
||||||
* and maintains a map of the current patch groups.
|
* manager will replace that reference with the current state of the full patch group so that the call event has the
|
||||||
*
|
* full patch group including all patched talkgroups or individual radios, which may not have been included in the
|
||||||
* Traffic channel calls will sometimes reference a patch group by simply using the patch group identifier. This
|
* patch group reference on the control or traffic channel.
|
||||||
* manager will replace that reference with the full patch group so that the call event has the full patch group
|
|
||||||
* including all of the patched groups, which may not have been included in the patch group reference on the
|
|
||||||
* traffic channel.
|
|
||||||
*/
|
*/
|
||||||
public class PatchGroupManager
|
public class PatchGroupManager
|
||||||
{
|
{
|
||||||
// private final static Logger mLog = LoggerFactory.getLogger(PatchGroupManager.class);
|
|
||||||
private Map<Integer,Long> mPatchGroupLastUpdatedMap = new HashMap<>();
|
|
||||||
private Map<Integer,PatchGroupIdentifier> mPatchGroupMap = new HashMap<>();
|
private Map<Integer,PatchGroupIdentifier> mPatchGroupMap = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,13 +54,13 @@ public class PatchGroupManager
|
|||||||
public void clear()
|
public void clear()
|
||||||
{
|
{
|
||||||
mPatchGroupMap.clear();
|
mPatchGroupMap.clear();
|
||||||
mPatchGroupLastUpdatedMap.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the patch group identifier to the set of managed patch groups. If the patch group is already being
|
* Adds the patch group identifier to the set of managed patch groups. If the patch group is already being
|
||||||
* managed, the new identifier is checked for additional patched talkgroups and they are added to the existing
|
* managed, the new patch group argument is checked for additional patched talkgroups and they are added to the
|
||||||
* managed patch group.
|
* existing managed patch group. Where the patch group version (super group sequence number) is different, the
|
||||||
|
* existing patch group is discarded and replaced with the updated version of the patch group.
|
||||||
*
|
*
|
||||||
* @param patchGroupIdentifier to add or update
|
* @param patchGroupIdentifier to add or update
|
||||||
*/
|
*/
|
||||||
@@ -76,8 +72,19 @@ public class PatchGroupManager
|
|||||||
{
|
{
|
||||||
if(mPatchGroupMap.containsKey(update.getPatchGroup().getValue()))
|
if(mPatchGroupMap.containsKey(update.getPatchGroup().getValue()))
|
||||||
{
|
{
|
||||||
PatchGroup current = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue();
|
PatchGroup existingPatchGroup = mPatchGroupMap.get(update.getPatchGroup().getValue()).getValue();
|
||||||
current.addPatchedGroups(update.getPatchedGroupIdentifiers());
|
|
||||||
|
//If the patch group version number is the same, this is an update
|
||||||
|
if(existingPatchGroup.getVersion() == update.getVersion())
|
||||||
|
{
|
||||||
|
existingPatchGroup.addPatchedTalkgroups(update.getPatchedTalkgroupIdentifiers());
|
||||||
|
existingPatchGroup.addPatchedRadios(update.getPatchedRadioIdentifiers());
|
||||||
|
}
|
||||||
|
//Otherwise, this is a replace operation.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mPatchGroupMap.put(update.getPatchGroup().getValue(), patchGroupIdentifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -109,7 +116,6 @@ public class PatchGroupManager
|
|||||||
{
|
{
|
||||||
int id = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
int id = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
||||||
mPatchGroupMap.remove(id);
|
mPatchGroupMap.remove(id);
|
||||||
mPatchGroupLastUpdatedMap.remove(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,23 +159,28 @@ public class PatchGroupManager
|
|||||||
*/
|
*/
|
||||||
public Identifier update(Identifier identifier)
|
public Identifier update(Identifier identifier)
|
||||||
{
|
{
|
||||||
if(identifier == null)
|
if(identifier != null && identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO)
|
||||||
{
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(identifier.getIdentifierClass() == IdentifierClass.USER && identifier.getRole() == Role.TO)
|
|
||||||
{
|
{
|
||||||
switch(identifier.getForm())
|
switch(identifier.getForm())
|
||||||
{
|
{
|
||||||
case PATCH_GROUP:
|
case TALKGROUP:
|
||||||
if(identifier instanceof PatchGroupIdentifier)
|
if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||||
|
{
|
||||||
|
Identifier mapValue = mPatchGroupMap.get(talkgroupIdentifier.getValue());
|
||||||
|
if (mapValue != null)
|
||||||
|
{
|
||||||
|
return mapValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PATCH_GROUP:
|
||||||
|
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
{
|
{
|
||||||
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
|
||||||
int patchGroupId = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
int patchGroupId = patchGroupIdentifier.getValue().getPatchGroup().getValue();
|
||||||
|
|
||||||
Identifier mapValue = mPatchGroupMap.get(patchGroupId);
|
Identifier mapValue = mPatchGroupMap.get(patchGroupId);
|
||||||
if (mapValue != null) {
|
if (mapValue != null)
|
||||||
|
{
|
||||||
return mapValue;
|
return mapValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,7 +21,6 @@ package io.github.dsheirer.module;
|
|||||||
|
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -50,7 +49,7 @@ public abstract class HistoryModule<T> extends Module implements Listener<T>
|
|||||||
*/
|
*/
|
||||||
public List<T> getItems()
|
public List<T> getItems()
|
||||||
{
|
{
|
||||||
return new ArrayList<>(mItems);
|
return new ArrayList<>(mItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
|
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
|
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
|
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(200);
|
||||||
private MessageHistory mMessageHistory = new MessageHistory(500);
|
private MessageHistory mMessageHistory = new MessageHistory(200);
|
||||||
private AbstractChannelState mChannelState;
|
private AbstractChannelState mChannelState;
|
||||||
private EventBus mEventBus;
|
private EventBus mEventBus;
|
||||||
protected Source mSource;
|
protected Source mSource;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,11 +23,14 @@ import io.github.dsheirer.message.IMessageProvider;
|
|||||||
import io.github.dsheirer.module.Module;
|
import io.github.dsheirer.module.Module;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base decoder class.
|
||||||
|
*/
|
||||||
public abstract class Decoder extends Module implements IMessageProvider
|
public abstract class Decoder extends Module implements IMessageProvider
|
||||||
{
|
{
|
||||||
/* This has to be a broadcaster in order for references to persist */
|
|
||||||
private Listener<IMessage> mMessageDistributor = new MessageDistributor();
|
private Listener<IMessage> mMessageDistributor = new MessageDistributor();
|
||||||
protected Listener<IMessage> mMessageListener;
|
protected Listener<IMessage> mMessageListener;
|
||||||
|
private boolean mRunning;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoder - parent class for all decoders, demodulators and components.
|
* Decoder - parent class for all decoders, demodulators and components.
|
||||||
@@ -36,14 +39,29 @@ public abstract class Decoder extends Module implements IMessageProvider
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts this decoder and sets the running flag to true.
|
||||||
|
*/
|
||||||
public void start()
|
public void start()
|
||||||
{
|
{
|
||||||
//no-op
|
mRunning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops this decoder and sets the running flag to false.
|
||||||
|
*/
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
//no-op
|
mRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this decoder is currently started and in a running state.
|
||||||
|
* @return true if running or false if not.
|
||||||
|
*/
|
||||||
|
public boolean isRunning()
|
||||||
|
{
|
||||||
|
return mRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset()
|
public void reset()
|
||||||
|
|||||||
@@ -39,11 +39,15 @@ import io.github.dsheirer.module.decode.am.AMDecoderState;
|
|||||||
import io.github.dsheirer.module.decode.am.DecodeConfigAM;
|
import io.github.dsheirer.module.decode.am.DecodeConfigAM;
|
||||||
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
|
||||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSDecoder;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSDecoderState;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSMessageFilter;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRDecoder;
|
import io.github.dsheirer.module.decode.dmr.DMRDecoder;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRDecoderState;
|
import io.github.dsheirer.module.decode.dmr.DMRDecoderState;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
|
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
|
||||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||||
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
|
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet;
|
||||||
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
|
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
|
||||||
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState;
|
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState;
|
||||||
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
|
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
|
||||||
@@ -77,16 +81,18 @@ import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
|||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FM;
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FM;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSM;
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSM;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState;
|
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25MessageFilterSet;
|
import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25P1MessageFilterSet;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK;
|
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK;
|
||||||
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
|
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
|
||||||
|
import io.github.dsheirer.module.decode.p25.phase2.message.filter.P25P2MessageFilterSet;
|
||||||
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
|
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
|
||||||
import io.github.dsheirer.module.decode.passport.PassportDecoder;
|
import io.github.dsheirer.module.decode.passport.PassportDecoder;
|
||||||
import io.github.dsheirer.module.decode.passport.PassportDecoderState;
|
import io.github.dsheirer.module.decode.passport.PassportDecoderState;
|
||||||
import io.github.dsheirer.module.decode.passport.PassportMessageFilter;
|
import io.github.dsheirer.module.decode.passport.PassportMessageFilter;
|
||||||
import io.github.dsheirer.module.decode.tait.Tait1200Decoder;
|
import io.github.dsheirer.module.decode.tait.Tait1200Decoder;
|
||||||
import io.github.dsheirer.module.decode.tait.Tait1200DecoderState;
|
import io.github.dsheirer.module.decode.tait.Tait1200DecoderState;
|
||||||
|
import io.github.dsheirer.module.decode.tait.Tait1200MessageFilter;
|
||||||
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
|
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
|
||||||
import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule;
|
import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
@@ -98,12 +104,14 @@ import java.util.List;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating decoder modules to use in a processing chain for any supported protocols.
|
||||||
|
*/
|
||||||
public class DecoderFactory
|
public class DecoderFactory
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DecoderFactory.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DecoderFactory.class);
|
||||||
|
|
||||||
private static final double FM_CHANNEL_BANDWIDTH = 12500.0;
|
private static final double FM_CHANNEL_BANDWIDTH = 12500.0;
|
||||||
private static final double DEMODULATED_AUDIO_SAMPLE_RATE = 8000.0;
|
private static final boolean AUDIO_FILTER_ENABLE = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of one primary decoder and any auxiliary decoders, as
|
* Returns a list of one primary decoder and any auxiliary decoders, as
|
||||||
@@ -179,6 +187,13 @@ public class DecoderFactory
|
|||||||
return modules;
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoder modules for APCO-25 Phase 2 decoder
|
||||||
|
* @param channel configuration
|
||||||
|
* @param userPreferences reference
|
||||||
|
* @param modules collection to add to
|
||||||
|
* @param aliasList for the channel
|
||||||
|
*/
|
||||||
private static void processP25Phase2(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList) {
|
private static void processP25Phase2(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList) {
|
||||||
modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration()));
|
modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration()));
|
||||||
|
|
||||||
@@ -188,6 +203,13 @@ public class DecoderFactory
|
|||||||
modules.add(new P25P2AudioModule(userPreferences, 1, aliasList));
|
modules.add(new P25P2AudioModule(userPreferences, 1, aliasList));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoder modules for APCO-25 Phase 1 decoder
|
||||||
|
* @param channel configuration
|
||||||
|
* @param userPreferences reference
|
||||||
|
* @param modules collection to add to
|
||||||
|
* @param aliasList for the channel
|
||||||
|
*/
|
||||||
private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigP25Phase1 decodeConfig) {
|
private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigP25Phase1 decodeConfig) {
|
||||||
DecodeConfigP25Phase1 p25Config = decodeConfig;
|
DecodeConfigP25Phase1 p25Config = decodeConfig;
|
||||||
|
|
||||||
@@ -228,16 +250,32 @@ public class DecoderFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoder modules for Passport decoder
|
||||||
|
* @param channel configuration
|
||||||
|
* @param modules collection to add to
|
||||||
|
* @param aliasList for the channel
|
||||||
|
* @param decodeConfig for the channel
|
||||||
|
*/
|
||||||
private static void processPassport(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig) {
|
private static void processPassport(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig) {
|
||||||
modules.add(new PassportDecoder(decodeConfig));
|
modules.add(new PassportDecoder(decodeConfig));
|
||||||
modules.add(new PassportDecoderState());
|
modules.add(new PassportDecoderState());
|
||||||
modules.add(new AudioModule(aliasList));
|
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
|
||||||
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
||||||
{
|
{
|
||||||
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
|
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoder modules for MPT-1327 decoder
|
||||||
|
* @param channelMapModel to use in calculating traffic channel frequencies
|
||||||
|
* @param channel configuration
|
||||||
|
* @param modules collection to add to
|
||||||
|
* @param aliasList for the channel
|
||||||
|
* @param channelType for control or traffic
|
||||||
|
* @param decodeConfig configuration
|
||||||
|
*/
|
||||||
private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig) {
|
private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig) {
|
||||||
DecodeConfigMPT1327 mptConfig = decodeConfig;
|
DecodeConfigMPT1327 mptConfig = decodeConfig;
|
||||||
ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName());
|
ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName());
|
||||||
@@ -250,12 +288,12 @@ public class DecoderFactory
|
|||||||
// not create a new segment if the processing chain finishes a bit after
|
// not create a new segment if the processing chain finishes a bit after
|
||||||
// actual call timeout.
|
// actual call timeout.
|
||||||
long maxAudioSegmentLengthMillis = (callTimeoutMilliseconds + 5000);
|
long maxAudioSegmentLengthMillis = (callTimeoutMilliseconds + 5000);
|
||||||
modules.add(new AudioModule(aliasList, AbstractAudioModule.DEFAULT_TIMESLOT, maxAudioSegmentLengthMillis));
|
modules.add(new AudioModule(aliasList, AbstractAudioModule.DEFAULT_TIMESLOT, maxAudioSegmentLengthMillis, AUDIO_FILTER_ENABLE));
|
||||||
|
|
||||||
SourceType sourceType = channel.getSourceConfiguration().getSourceType();
|
SourceType sourceType = channel.getSourceConfiguration().getSourceType();
|
||||||
if(sourceType == SourceType.TUNER || sourceType == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
if(sourceType == SourceType.TUNER || sourceType == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
||||||
{
|
{
|
||||||
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
|
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(channelType == ChannelType.STANDARD)
|
if(channelType == ChannelType.STANDARD)
|
||||||
@@ -280,27 +318,48 @@ public class DecoderFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoder modules for LTR-Net decoder
|
||||||
|
* @param channel configuration
|
||||||
|
* @param modules collection to add to
|
||||||
|
* @param aliasList for the channel
|
||||||
|
* @param decodeConfig for the channel
|
||||||
|
*/
|
||||||
private static void processLTRNet(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfigLTRNet decodeConfig) {
|
private static void processLTRNet(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfigLTRNet decodeConfig) {
|
||||||
modules.add(new LTRNetDecoder(decodeConfig));
|
modules.add(new LTRNetDecoder(decodeConfig));
|
||||||
modules.add(new LTRNetDecoderState());
|
modules.add(new LTRNetDecoderState());
|
||||||
modules.add(new AudioModule(aliasList));
|
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
|
||||||
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
||||||
{
|
{
|
||||||
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
|
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoder modules for LTR decoder
|
||||||
|
* @param channel configuration
|
||||||
|
* @param modules collection to add to
|
||||||
|
* @param aliasList for the channel
|
||||||
|
* @param decodeConfig for the channel
|
||||||
|
*/
|
||||||
private static void processLTRStandard(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfigLTRStandard decodeConfig) {
|
private static void processLTRStandard(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfigLTRStandard decodeConfig) {
|
||||||
MessageDirection direction = decodeConfig.getMessageDirection();
|
MessageDirection direction = decodeConfig.getMessageDirection();
|
||||||
modules.add(new LTRStandardDecoder(null, direction));
|
modules.add(new LTRStandardDecoder(direction));
|
||||||
modules.add(new LTRStandardDecoderState());
|
modules.add(new LTRStandardDecoderState());
|
||||||
modules.add(new AudioModule(aliasList));
|
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
|
||||||
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
||||||
{
|
{
|
||||||
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
|
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoder modules for Narrow Band FM decoder
|
||||||
|
* @param channel configuration
|
||||||
|
* @param modules collection to add to
|
||||||
|
* @param aliasList for the channel
|
||||||
|
* @param decodeConfig for the channel
|
||||||
|
*/
|
||||||
private static void processNBFM(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig)
|
private static void processNBFM(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig)
|
||||||
{
|
{
|
||||||
if(!(decodeConfig instanceof DecodeConfigNBFM))
|
if(!(decodeConfig instanceof DecodeConfigNBFM))
|
||||||
@@ -312,9 +371,16 @@ public class DecoderFactory
|
|||||||
DecodeConfigNBFM decodeConfigNBFM = (DecodeConfigNBFM)decodeConfig;
|
DecodeConfigNBFM decodeConfigNBFM = (DecodeConfigNBFM)decodeConfig;
|
||||||
modules.add(new NBFMDecoder(decodeConfigNBFM));
|
modules.add(new NBFMDecoder(decodeConfigNBFM));
|
||||||
modules.add(new NBFMDecoderState(channel.getName(), decodeConfigNBFM));
|
modules.add(new NBFMDecoderState(channel.getName(), decodeConfigNBFM));
|
||||||
modules.add(new AudioModule(aliasList));
|
modules.add(new AudioModule(aliasList, 0, 60000, decodeConfigNBFM.isAudioFilter()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoder modules for AM decoder
|
||||||
|
* @param channel configuration
|
||||||
|
* @param modules collection to add to
|
||||||
|
* @param aliasList for the channel
|
||||||
|
* @param decodeConfig for the channel
|
||||||
|
*/
|
||||||
private static void processAM(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig) {
|
private static void processAM(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig) {
|
||||||
|
|
||||||
if(!(decodeConfig instanceof DecodeConfigAM))
|
if(!(decodeConfig instanceof DecodeConfigAM))
|
||||||
@@ -326,7 +392,7 @@ public class DecoderFactory
|
|||||||
DecodeConfigAM decodeConfigAM = (DecodeConfigAM) decodeConfig;
|
DecodeConfigAM decodeConfigAM = (DecodeConfigAM) decodeConfig;
|
||||||
modules.add(new AMDecoder(decodeConfigAM));
|
modules.add(new AMDecoder(decodeConfigAM));
|
||||||
modules.add(new AMDecoderState(channel.getName(), decodeConfigAM));
|
modules.add(new AMDecoderState(channel.getName(), decodeConfigAM));
|
||||||
modules.add(new AudioModule(aliasList));
|
modules.add(new AudioModule(aliasList, 0, 60000, AUDIO_FILTER_ENABLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -381,7 +447,7 @@ public class DecoderFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a list of auxiliary decoders, as specified in the configuration
|
* Constructs a list of auxiliary decoders, as specified in the channel configuration
|
||||||
*
|
*
|
||||||
* @param config - auxiliary configuration
|
* @param config - auxiliary configuration
|
||||||
* @return - list of auxiliary decoders
|
* @return - list of auxiliary decoders
|
||||||
@@ -396,6 +462,10 @@ public class DecoderFactory
|
|||||||
{
|
{
|
||||||
switch(auxDecoder)
|
switch(auxDecoder)
|
||||||
{
|
{
|
||||||
|
case DCS:
|
||||||
|
modules.add(new DCSDecoder());
|
||||||
|
modules.add(new DCSDecoderState());
|
||||||
|
break;
|
||||||
case FLEETSYNC2:
|
case FLEETSYNC2:
|
||||||
modules.add(new Fleetsync2Decoder());
|
modules.add(new Fleetsync2Decoder());
|
||||||
modules.add(new Fleetsync2DecoderState());
|
modules.add(new Fleetsync2DecoderState());
|
||||||
@@ -427,7 +497,7 @@ public class DecoderFactory
|
|||||||
*/
|
*/
|
||||||
public static FilterSet<IMessage> getMessageFilters(List<Module> modules)
|
public static FilterSet<IMessage> getMessageFilters(List<Module> modules)
|
||||||
{
|
{
|
||||||
FilterSet<IMessage> filterSet = new FilterSet<>();
|
FilterSet<IMessage> filterSet = new FilterSet<>("Message Filters");
|
||||||
|
|
||||||
for(Module module : modules)
|
for(Module module : modules)
|
||||||
{
|
{
|
||||||
@@ -437,25 +507,28 @@ public class DecoderFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we don't have any filters, add an ALL-PASS filter */
|
//Add an all-others filter as a catch-all for anything that isn't handled by the decoder filters.
|
||||||
if(filterSet.getFilters().isEmpty())
|
filterSet.addFilter(new AllPassFilter<>("All Other Messages Filter"));
|
||||||
{
|
|
||||||
filterSet.addFilter(new AllPassFilter<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return filterSet;
|
return filterSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a set of IMessageFilter objects (FilterSets or Filters) that
|
* Returns a set of IMessageFilter objects (FilterSets or Filters) that
|
||||||
* can process all of the messages produced by the specified decoder type.
|
* can process all messages produced by the specified decoder type.
|
||||||
*/
|
*/
|
||||||
public static List<IFilter<IMessage>> getMessageFilter(DecoderType decoder)
|
public static List<IFilter<IMessage>> getMessageFilter(DecoderType decoder)
|
||||||
{
|
{
|
||||||
ArrayList<IFilter<IMessage>> filters = new ArrayList<>();
|
List<IFilter<IMessage>> filters = new ArrayList<>();
|
||||||
|
|
||||||
switch(decoder)
|
switch(decoder)
|
||||||
{
|
{
|
||||||
|
case DCS:
|
||||||
|
filters.add(new DCSMessageFilter());
|
||||||
|
break;
|
||||||
|
case DMR:
|
||||||
|
filters.add(new DmrMessageFilterSet());
|
||||||
|
break;
|
||||||
case FLEETSYNC2:
|
case FLEETSYNC2:
|
||||||
filters.add(new FleetsyncMessageFilter());
|
filters.add(new FleetsyncMessageFilter());
|
||||||
break;
|
break;
|
||||||
@@ -475,13 +548,16 @@ public class DecoderFactory
|
|||||||
filters.add(new MPT1327MessageFilter());
|
filters.add(new MPT1327MessageFilter());
|
||||||
break;
|
break;
|
||||||
case P25_PHASE1:
|
case P25_PHASE1:
|
||||||
filters.add(new P25MessageFilterSet());
|
filters.add(new P25P1MessageFilterSet());
|
||||||
|
break;
|
||||||
|
case P25_PHASE2:
|
||||||
|
filters.add(new P25P2MessageFilterSet());
|
||||||
break;
|
break;
|
||||||
case PASSPORT:
|
case PASSPORT:
|
||||||
filters.add(new PassportMessageFilter());
|
filters.add(new PassportMessageFilter());
|
||||||
break;
|
break;
|
||||||
case DMR:
|
case TAIT_1200:
|
||||||
//filters.add(new DMR) //todo: not finished
|
filters.add(new Tait1200MessageFilter());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -518,7 +594,7 @@ public class DecoderFactory
|
|||||||
case P25_PHASE2:
|
case P25_PHASE2:
|
||||||
return new DecodeConfigP25Phase2();
|
return new DecodeConfigP25Phase2();
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("DecodeConfigFactory - unknown decoder type [" + decoder.toString() + "]");
|
throw new IllegalArgumentException("DecodeConfigFactory - unknown decoder type [" + decoder + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
package io.github.dsheirer.module.decode;
|
package io.github.dsheirer.module.decode;
|
||||||
|
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +38,7 @@ public enum DecoderType
|
|||||||
P25_PHASE2("P25 Phase 2", "P25-2", Protocol.APCO25_PHASE2),
|
P25_PHASE2("P25 Phase 2", "P25-2", Protocol.APCO25_PHASE2),
|
||||||
|
|
||||||
//Auxiliary Decoders
|
//Auxiliary Decoders
|
||||||
|
DCS("Digital Coded Squelch (DCS)", "DCS", Protocol.DCS),
|
||||||
FLEETSYNC2("Fleetsync II", "Fleetsync2", Protocol.FLEETSYNC),
|
FLEETSYNC2("Fleetsync II", "Fleetsync2", Protocol.FLEETSYNC),
|
||||||
LJ_1200("LJ1200 173.075", "LJ1200", Protocol.LOJACK),
|
LJ_1200("LJ1200 173.075", "LJ1200", Protocol.LOJACK),
|
||||||
MDC1200("MDC1200", "MDC1200", Protocol.MDC1200),
|
MDC1200("MDC1200", "MDC1200", Protocol.MDC1200),
|
||||||
@@ -73,7 +73,8 @@ public enum DecoderType
|
|||||||
* Auxiliary decoders that operate on in-band signalling in the decoded audio channel
|
* Auxiliary decoders that operate on in-band signalling in the decoded audio channel
|
||||||
*/
|
*/
|
||||||
public static final EnumSet<DecoderType> AUX_DECODERS =
|
public static final EnumSet<DecoderType> AUX_DECODERS =
|
||||||
EnumSet.of(DecoderType.FLEETSYNC2,
|
EnumSet.of(DecoderType.DCS,
|
||||||
|
DecoderType.FLEETSYNC2,
|
||||||
DecoderType.LJ_1200,
|
DecoderType.LJ_1200,
|
||||||
DecoderType.MDC1200,
|
DecoderType.MDC1200,
|
||||||
DecoderType.TAIT_1200);
|
DecoderType.TAIT_1200);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import io.github.dsheirer.identifier.Identifier;
|
|||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
import io.github.dsheirer.module.decode.p25.identifier.channel.StandardChannel;
|
import io.github.dsheirer.module.decode.p25.identifier.channel.StandardChannel;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.source.ISourceEventListener;
|
import io.github.dsheirer.source.ISourceEventListener;
|
||||||
@@ -100,9 +101,8 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
|
|||||||
|
|
||||||
if(mDecodeEvent == null)
|
if(mDecodeEvent == null)
|
||||||
{
|
{
|
||||||
mDecodeEvent = DecodeEvent.builder(System.currentTimeMillis())
|
mDecodeEvent = DecodeEvent.builder(DecodeEventType.CALL, System.currentTimeMillis())
|
||||||
.channel(mChannelDescriptor)
|
.channel(mChannelDescriptor)
|
||||||
.eventDescription("CALL")
|
|
||||||
.details(getDecoderType().name())
|
.details(getDecoderType().name())
|
||||||
.identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers()))
|
.identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers()))
|
||||||
.build();
|
.build();
|
||||||
@@ -121,6 +121,7 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
|
|||||||
startCallEvent();
|
startCallEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getIdentifierCollection().update(getTalkgroupIdentifier());
|
||||||
mDecodeEvent.update(System.currentTimeMillis());
|
mDecodeEvent.update(System.currentTimeMillis());
|
||||||
broadcast(mDecodeEvent);
|
broadcast(mDecodeEvent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ public abstract class DecodeConfigAnalog extends DecodeConfiguration implements
|
|||||||
{
|
{
|
||||||
BW_3_0("3.0 kHz", 3000.0),
|
BW_3_0("3.0 kHz", 3000.0),
|
||||||
BW_5_0("5.0 kHz", 5000.0),
|
BW_5_0("5.0 kHz", 5000.0),
|
||||||
|
BW_7_5("7.5 kHz", 7500.0),
|
||||||
BW_8_33("8.33 kHz", 8333.0),
|
BW_8_33("8.33 kHz", 8333.0),
|
||||||
BW_12_5("12.5 kHz", 12500.0),
|
BW_12_5("12.5 kHz", 12500.0),
|
||||||
BW_15_0("15.0 kHz", 15000.0),
|
BW_15_0("15.0 kHz", 15000.0),
|
||||||
@@ -168,7 +169,7 @@ public abstract class DecodeConfigAnalog extends DecodeConfiguration implements
|
|||||||
public static EnumSet<Bandwidth> AM_BANDWIDTHS = EnumSet.of(BW_3_0, BW_5_0, BW_8_33, BW_15_0, BW_25_0);
|
public static EnumSet<Bandwidth> AM_BANDWIDTHS = EnumSet.of(BW_3_0, BW_5_0, BW_8_33, BW_15_0, BW_25_0);
|
||||||
|
|
||||||
//FM demodulator channel bandwidth options
|
//FM demodulator channel bandwidth options
|
||||||
public static EnumSet<Bandwidth> FM_BANDWIDTHS = EnumSet.of(BW_12_5, BW_25_0);
|
public static EnumSet<Bandwidth> FM_BANDWIDTHS = EnumSet.of(BW_7_5, BW_12_5, BW_25_0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this entry is valid for the AM decoder.
|
* Indicates if this entry is valid for the AM decoder.
|
||||||
|
|||||||
286
src/main/java/io/github/dsheirer/module/decode/dcs/DCSCode.java
Normal file
286
src/main/java/io/github/dsheirer/module/decode/dcs/DCSCode.java
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.dcs;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCS codes as defined in ETSI TS 103 236 V1.1.1
|
||||||
|
*
|
||||||
|
* Code values are the bit reversed integer values of the 23-bit binary sequence representation. The values are bit
|
||||||
|
* reversed for normal (N) codes and as specified in the ETSI chart for the inverted (I) codes to align with the
|
||||||
|
* transmitted format.
|
||||||
|
*/
|
||||||
|
public enum DCSCode
|
||||||
|
{
|
||||||
|
//Inverted codes
|
||||||
|
I023(7747603),
|
||||||
|
I025(7043093),
|
||||||
|
I026(6674454),
|
||||||
|
I031(5371929),
|
||||||
|
I032(6248474),
|
||||||
|
I043(5990435),
|
||||||
|
I047(1038375),
|
||||||
|
I051(8169513),
|
||||||
|
I054(7292972),
|
||||||
|
I065(6101045),
|
||||||
|
I071(6789177),
|
||||||
|
I072(6895674),
|
||||||
|
I073(3041339),
|
||||||
|
I074(7632956),
|
||||||
|
I114(3532876),
|
||||||
|
I115(7518285),
|
||||||
|
I116(8132686),
|
||||||
|
I125(505941),
|
||||||
|
I131(4012121),
|
||||||
|
I132(3381338),
|
||||||
|
I134(3070044),
|
||||||
|
I143(3647587),
|
||||||
|
I152(2017386),
|
||||||
|
I155(4511853),
|
||||||
|
I156(4880494),
|
||||||
|
I162(7063666),
|
||||||
|
I165(3266677),
|
||||||
|
I172(391290),
|
||||||
|
I174(1620092),
|
||||||
|
I205(7248005),
|
||||||
|
I223(6875283),
|
||||||
|
I226(8063126),
|
||||||
|
I243(4569251),
|
||||||
|
I244(2074788),
|
||||||
|
I245(5830821),
|
||||||
|
I251(6453417),
|
||||||
|
I261(1538225),
|
||||||
|
I263(6195379),
|
||||||
|
I265(4442293),
|
||||||
|
I271(7948473),
|
||||||
|
I306(850118),
|
||||||
|
I311(3725513),
|
||||||
|
I315(7104717),
|
||||||
|
I331(2353369),
|
||||||
|
I343(2717923),
|
||||||
|
I346(3840230),
|
||||||
|
I351(964841),
|
||||||
|
I364(6838516),
|
||||||
|
I365(3082485),
|
||||||
|
I371(1411321),
|
||||||
|
I411(7825673),
|
||||||
|
I412(7981322),
|
||||||
|
I413(4102411),
|
||||||
|
I423(4954387),
|
||||||
|
I431(7100697),
|
||||||
|
I432(6486298),
|
||||||
|
I445(8096037),
|
||||||
|
I464(2615604),
|
||||||
|
I465(6338869),
|
||||||
|
I466(7215414),
|
||||||
|
I503(3959107),
|
||||||
|
I506(3115334),
|
||||||
|
I516(4307278),
|
||||||
|
I532(932186),
|
||||||
|
I546(1698150),
|
||||||
|
I565(817525),
|
||||||
|
I606(6134150),
|
||||||
|
I612(6756746),
|
||||||
|
I624(1005972),
|
||||||
|
I627(129431),
|
||||||
|
I631(7506329),
|
||||||
|
I632(8137114),
|
||||||
|
I654(4995500),
|
||||||
|
I662(2390450),
|
||||||
|
I664(3750324),
|
||||||
|
I703(2275779),
|
||||||
|
I712(776650),
|
||||||
|
I723(3770835),
|
||||||
|
I731(1984985),
|
||||||
|
I732(1108442),
|
||||||
|
I734(895452),
|
||||||
|
I743(1366499),
|
||||||
|
I754(2161132),
|
||||||
|
|
||||||
|
//Normal codes
|
||||||
|
N023(6557239),
|
||||||
|
N025(5508971),
|
||||||
|
N026(3411411),
|
||||||
|
N031(4984773),
|
||||||
|
N032(2887037),
|
||||||
|
N043(6425453),
|
||||||
|
N047(7474680),
|
||||||
|
N051(4852383),
|
||||||
|
N054(1706363),
|
||||||
|
N065(5639261),
|
||||||
|
N071(5115123),
|
||||||
|
N072(3018315),
|
||||||
|
N073(7211834),
|
||||||
|
N074(1969943),
|
||||||
|
N114(1641430),
|
||||||
|
N115(5836455),
|
||||||
|
N116(3738655),
|
||||||
|
N125(5574384),
|
||||||
|
N131(5049950),
|
||||||
|
N132(2952422),
|
||||||
|
N134(1904058),
|
||||||
|
N143(6490870),
|
||||||
|
N152(2820540),
|
||||||
|
N155(5967249),
|
||||||
|
N156(3870505),
|
||||||
|
N162(2558443),
|
||||||
|
N165(5705158),
|
||||||
|
N172(3084240),
|
||||||
|
N174(2035340),
|
||||||
|
N205(5278907),
|
||||||
|
N223(6589323),
|
||||||
|
N226(3442799),
|
||||||
|
N243(6459089),
|
||||||
|
N244(1215228),
|
||||||
|
N245(5410701),
|
||||||
|
N251(4886307),
|
||||||
|
N261(4624244),
|
||||||
|
N263(6719677),
|
||||||
|
N265(5671393),
|
||||||
|
N271(5146959),
|
||||||
|
N306(3248024),
|
||||||
|
N311(4820366),
|
||||||
|
N315(5868315),
|
||||||
|
N331(5082082),
|
||||||
|
N343(6524746),
|
||||||
|
N346(3378350),
|
||||||
|
N351(4951736),
|
||||||
|
N364(1543435),
|
||||||
|
N365(5736570),
|
||||||
|
N371(5212372),
|
||||||
|
N411(4737911),
|
||||||
|
N412(2640335),
|
||||||
|
N413(6835390),
|
||||||
|
N423(6573289),
|
||||||
|
N431(5000475),
|
||||||
|
N432(2903971),
|
||||||
|
N445(5392623),
|
||||||
|
N464(1461234),
|
||||||
|
N465(5656195),
|
||||||
|
N466(3558459),
|
||||||
|
N503(6376222),
|
||||||
|
N506(3229946),
|
||||||
|
N516(3755713),
|
||||||
|
N532(2969144),
|
||||||
|
N546(3361740),
|
||||||
|
N565(5721880),
|
||||||
|
N606(3198173),
|
||||||
|
N612(2673779),
|
||||||
|
N624(1363320),
|
||||||
|
N627(7655360),
|
||||||
|
N631(5032103),
|
||||||
|
N632(2935327),
|
||||||
|
N654(1756697),
|
||||||
|
N662(2543378),
|
||||||
|
N664(1494606),
|
||||||
|
N703(6409890),
|
||||||
|
N712(2739688),
|
||||||
|
N723(6670542),
|
||||||
|
N731(5097788),
|
||||||
|
N732(3001220),
|
||||||
|
N734(1952472),
|
||||||
|
N743(6540692),
|
||||||
|
N754(1822594),
|
||||||
|
UNKNOWN(0);
|
||||||
|
|
||||||
|
private int mValue;
|
||||||
|
|
||||||
|
DCSCode(int value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inverted DCS codes
|
||||||
|
*/
|
||||||
|
public static final EnumSet<DCSCode> INVERTED_CODES = EnumSet.range(I023, I754);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard DCS codes
|
||||||
|
*/
|
||||||
|
public static final EnumSet<DCSCode> STANDARD_CODES = EnumSet.range(N023,N754);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup map for quickly finding a DCS code from the transmitted value.
|
||||||
|
*/
|
||||||
|
private static final Map<Integer,DCSCode> CODE_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
for(DCSCode code: EnumSet.range(I023, N754))
|
||||||
|
{
|
||||||
|
CODE_MAP.put(code.getValue(), code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integer value of the code from transmitted bits.
|
||||||
|
* @return value
|
||||||
|
*/
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this is an inverted DCS code (true) or a normal DCS code (false).
|
||||||
|
* @return true if inverted.
|
||||||
|
*/
|
||||||
|
public boolean isInverted()
|
||||||
|
{
|
||||||
|
return INVERTED_CODES.contains(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "DCS-" + name().substring(1, 4) + (isInverted() ? " Inverted" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the value maps to a known DCS code.
|
||||||
|
* @param value to test
|
||||||
|
* @return true if the value matches a DCS code.
|
||||||
|
*/
|
||||||
|
public static boolean hasValue(int value)
|
||||||
|
{
|
||||||
|
return CODE_MAP.containsKey(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the DCS code that matches the value, otherwise return UNKNOWN.
|
||||||
|
* @param value to lookup
|
||||||
|
* @return matching DCS code or UNKNOWN.
|
||||||
|
*/
|
||||||
|
public static DCSCode fromValue(int value)
|
||||||
|
{
|
||||||
|
DCSCode code = CODE_MAP.get(value);
|
||||||
|
|
||||||
|
if(code == null)
|
||||||
|
{
|
||||||
|
code = UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.dcs;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||||
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
|
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||||
|
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||||
|
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||||
|
import io.github.dsheirer.module.decode.Decoder;
|
||||||
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.sample.real.IRealBufferListener;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digital Coded Squelch (DCS) demodulator/decoder designed to work with FM demodulated 8 kHz input audio samples and
|
||||||
|
* decode 134.4-baud signalling present in the unfiltered demodulated audio stream.
|
||||||
|
*
|
||||||
|
* This decoder monitors the incoming sample stream looking for either a positive or negative sequence of samples where
|
||||||
|
* the slope of these values exceeds a threshold and causes the symbol state to flip. The symbol state remains across
|
||||||
|
* successive baud periods until the next opposing slope state change sequence is detected.
|
||||||
|
*
|
||||||
|
* This decoder uses a slope detector to decode the 134.4 bits per second stream and identify the DCS code. It is
|
||||||
|
* designed to work with 8 kHz audio sample rate. Each baud period is (8,000 / 134.4) = 59.52 samples per symbol and
|
||||||
|
* a full DCS tone sequence is 23 bits * 59.52 samples/bit = 1369 samples. The DCS tone sequence is continuously
|
||||||
|
* transmitted at a rate of 134.4 bits-per-second / 23 = 5.84 repeats per second while the source continues transmitting.
|
||||||
|
*/
|
||||||
|
public class DCSDecoder extends Decoder implements IRealBufferListener, Listener<float[]>
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(DCSDecoder.class);
|
||||||
|
//Slope calculation sumXX value is always the same for 59 evenly spaced samples
|
||||||
|
private static final double SLOPE_CALCULATION_SUM_XX = 2247.5;
|
||||||
|
private static final float BAUD_LENGTH = 8000.0f / 134.4f;
|
||||||
|
private static final float SLOPE_THRESHOLD = 0.002750f;
|
||||||
|
private static final int MAX_ONES_SEQUENCE = 6;
|
||||||
|
private static final int OVERLAP = (int)Math.ceil(BAUD_LENGTH);
|
||||||
|
private static final int POST_TRANSITION_SAMPLES_TO_SKIP = 30;
|
||||||
|
private static final int SLOPE_CALCULATION_PERIOD = 30;
|
||||||
|
private static final int IDEAL_SYMBOL_TRANSITION_MIN = 11;
|
||||||
|
private static final int IDEAL_SYMBOL_TRANSITION_MAX = 19;
|
||||||
|
private static final int CODE_MASK = 0x7FFFFF; //23-bit mask
|
||||||
|
private static float[] sLowPassFilterCoefficients;
|
||||||
|
private boolean mSymbol = false;
|
||||||
|
private float mBaudCounter = 0f;
|
||||||
|
private float mMaxSlope = 0;
|
||||||
|
private float[] mResidual = new float[OVERLAP];
|
||||||
|
private int mCode = 0;
|
||||||
|
private int mExcessiveOneSequenceCounter = 0;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||||
|
.sampleRate(8000)
|
||||||
|
.gridDensity(16)
|
||||||
|
.oddLength(true)
|
||||||
|
.passBandCutoff(200)
|
||||||
|
.passBandAmplitude(1.0)
|
||||||
|
.passBandRipple(0.01)
|
||||||
|
.stopBandStart(300)
|
||||||
|
.stopBandAmplitude(0.0)
|
||||||
|
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
|
||||||
|
|
||||||
|
if(designer.isValid())
|
||||||
|
{
|
||||||
|
sLowPassFilterCoefficients = designer.getImpulseResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(FilterDesignException fde)
|
||||||
|
{
|
||||||
|
mLog.error("Filter design error", fde);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public DCSDecoder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder type
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DecoderType getDecoderType()
|
||||||
|
{
|
||||||
|
return DecoderType.DCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the IRealBufferListener interface
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Listener<float[]> getBufferListener()
|
||||||
|
{
|
||||||
|
return this::receive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the demodulated 8 kHz audio samples to extract the 300-baud LTR signalling and delivers the decoded
|
||||||
|
* symbol array to the registered listener.
|
||||||
|
* @param samples to demodulate.
|
||||||
|
*/
|
||||||
|
public void receive(float[] samples)
|
||||||
|
{
|
||||||
|
if(getMessageListener() != null)
|
||||||
|
{
|
||||||
|
float[] filtered = mLowPassFilter.filter(samples);
|
||||||
|
float[] buffer = new float[filtered.length + OVERLAP];
|
||||||
|
int samplesToSkip = 0;
|
||||||
|
int timingAdjust = 0;
|
||||||
|
float slope = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.arraycopy(mResidual, 0, buffer, 0, mResidual.length);
|
||||||
|
System.arraycopy(filtered, 0, buffer, mResidual.length, filtered.length);
|
||||||
|
System.arraycopy(filtered, filtered.length - OVERLAP, mResidual, 0, OVERLAP);
|
||||||
|
|
||||||
|
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
|
||||||
|
{
|
||||||
|
//Don't calculate slope if we're within the baud period following the last symbol transition
|
||||||
|
if(samplesToSkip > 0)
|
||||||
|
{
|
||||||
|
samplesToSkip--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slope = calculateSlope(buffer, bufferPointer);
|
||||||
|
|
||||||
|
if(mSymbol)
|
||||||
|
{
|
||||||
|
//Look for crest of slope at lowest point below the negative slope threshold for transition
|
||||||
|
if(slope > mMaxSlope && mMaxSlope < -SLOPE_THRESHOLD)
|
||||||
|
{
|
||||||
|
mSymbol = false;
|
||||||
|
|
||||||
|
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 19 samples
|
||||||
|
if(mBaudCounter < IDEAL_SYMBOL_TRANSITION_MIN)
|
||||||
|
{
|
||||||
|
timingAdjust = 1;
|
||||||
|
}
|
||||||
|
else if(mBaudCounter > IDEAL_SYMBOL_TRANSITION_MAX)
|
||||||
|
{
|
||||||
|
timingAdjust = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timingAdjust = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBaudCounter += timingAdjust;
|
||||||
|
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
|
||||||
|
}
|
||||||
|
else if(slope < mMaxSlope)
|
||||||
|
{
|
||||||
|
mMaxSlope = slope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Loop for crest of slope at the highest point above the positive threshold
|
||||||
|
if(slope < mMaxSlope && mMaxSlope > SLOPE_THRESHOLD)
|
||||||
|
{
|
||||||
|
mSymbol = true;
|
||||||
|
|
||||||
|
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 19 samples
|
||||||
|
if(mBaudCounter < IDEAL_SYMBOL_TRANSITION_MIN)
|
||||||
|
{
|
||||||
|
timingAdjust = 1;
|
||||||
|
}
|
||||||
|
else if(mBaudCounter > IDEAL_SYMBOL_TRANSITION_MAX)
|
||||||
|
{
|
||||||
|
timingAdjust = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timingAdjust = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBaudCounter += timingAdjust;
|
||||||
|
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
|
||||||
|
}
|
||||||
|
else if(slope > mMaxSlope)
|
||||||
|
{
|
||||||
|
mMaxSlope = slope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mBaudCounter++;
|
||||||
|
|
||||||
|
//Update the current code value once we've processed a baud/symbol period of samples
|
||||||
|
if(mBaudCounter > BAUD_LENGTH)
|
||||||
|
{
|
||||||
|
//Update the code value adding in the current symbol
|
||||||
|
mCode = (Integer.rotateLeft(mCode, 1) + (mSymbol ? 1 : 0)) & CODE_MASK;
|
||||||
|
if(DCSCode.hasValue(mCode))
|
||||||
|
{
|
||||||
|
getMessageListener().receive(new DCSMessage(DCSCode.fromValue(mCode), System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
mBaudCounter -= BAUD_LENGTH;
|
||||||
|
|
||||||
|
//Check for a continuous sequence of ones that exceeds the max (6) for any DCS code and
|
||||||
|
//flip/revert the symbol state back to zero.
|
||||||
|
if(mSymbol)
|
||||||
|
{
|
||||||
|
mExcessiveOneSequenceCounter++;
|
||||||
|
|
||||||
|
if(mExcessiveOneSequenceCounter > MAX_ONES_SEQUENCE)
|
||||||
|
{
|
||||||
|
mSymbol = false;
|
||||||
|
mMaxSlope = -1f;
|
||||||
|
mExcessiveOneSequenceCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mExcessiveOneSequenceCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.warn("Unexpected error while processing DCS samples", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the slope of the 30 samples in the values array starting at the specified offset. The baud period
|
||||||
|
* is slightly more than 59 samples, however the voltage state transition occurs over a 30-sample period.
|
||||||
|
*
|
||||||
|
* Note: code modified from Apache Commons Math library, SimpleRegression class.
|
||||||
|
* @param samples to load from
|
||||||
|
* @param offset to the sample start for the slope calculation
|
||||||
|
*/
|
||||||
|
public float calculateSlope(float[] samples, int offset)
|
||||||
|
{
|
||||||
|
/** mean of accumulated x & y values, used in updating formulas */
|
||||||
|
double xbar = 0;
|
||||||
|
double ybar = samples[offset];
|
||||||
|
|
||||||
|
// double sumXX = 0;
|
||||||
|
double sumXY = 0;
|
||||||
|
double fact1, dx, dy;
|
||||||
|
|
||||||
|
for(int x = 1; x < SLOPE_CALCULATION_PERIOD; x++)
|
||||||
|
{
|
||||||
|
fact1 = 1.0 + x;
|
||||||
|
// final double fact2 = x / (1.0 + x);
|
||||||
|
dx = x - xbar;
|
||||||
|
dy = samples[offset + x] - ybar;
|
||||||
|
// sumXX += dx * dx * fact2;
|
||||||
|
sumXY += dx * dy * (x / (1.0 + x));
|
||||||
|
xbar += dx / fact1;
|
||||||
|
ybar += dy / fact1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Transfer the sumXX value to SLOPE_CALCULATION_SUM_XX since it's a constant value
|
||||||
|
// mLog.info("SumXX: " + sumXX);
|
||||||
|
|
||||||
|
return (float)(sumXY / SLOPE_CALCULATION_SUM_XX);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.dcs;
|
||||||
|
|
||||||
|
import io.github.dsheirer.channel.state.DecoderState;
|
||||||
|
import io.github.dsheirer.channel.state.DecoderStateEvent;
|
||||||
|
import io.github.dsheirer.message.IMessage;
|
||||||
|
import io.github.dsheirer.module.decode.DecoderType;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains the decoder state from the stream of decoded DCS code messages.
|
||||||
|
*/
|
||||||
|
public class DCSDecoderState extends DecoderState
|
||||||
|
{
|
||||||
|
private Map<DCSCode,Integer> mDcsCodeCountsMap = new HashMap<>();
|
||||||
|
private DCSCode mCurrentCode = DCSCode.UNKNOWN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public DCSDecoderState()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DecoderType getDecoderType()
|
||||||
|
{
|
||||||
|
return DecoderType.DCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(IMessage message)
|
||||||
|
{
|
||||||
|
if(message instanceof DCSMessage dcsMessage)
|
||||||
|
{
|
||||||
|
mCurrentCode = dcsMessage.getDCSCode();
|
||||||
|
|
||||||
|
if(!mDcsCodeCountsMap.containsKey(mCurrentCode))
|
||||||
|
{
|
||||||
|
mDcsCodeCountsMap.put(mCurrentCode, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mDcsCodeCountsMap.put(mCurrentCode, mDcsCodeCountsMap.get(mCurrentCode) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
getIdentifierCollection().update(dcsMessage.getIdentifiers());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveDecoderStateEvent(DecoderStateEvent event)
|
||||||
|
{
|
||||||
|
switch(event.getEvent())
|
||||||
|
{
|
||||||
|
case REQUEST_RESET:
|
||||||
|
resetState();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getActivitySummary()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("=============================\n");
|
||||||
|
sb.append("Decoder:\tDigital Coded Squelch (DCS)\n\n");
|
||||||
|
if(mDcsCodeCountsMap.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(" Detected Codes: (none)\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(Map.Entry<DCSCode,Integer> entry: mDcsCodeCountsMap.entrySet())
|
||||||
|
{
|
||||||
|
sb.append(" ").append(entry.getKey()).append(" - Count: ").append(entry.getValue());
|
||||||
|
|
||||||
|
if(entry.getKey().equals(mCurrentCode))
|
||||||
|
{
|
||||||
|
sb.append(" - Current\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.dcs;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
|
||||||
|
import io.github.dsheirer.message.Message;
|
||||||
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digital Coded Squelch (DCS) tone detected message
|
||||||
|
*/
|
||||||
|
public class DCSMessage extends Message
|
||||||
|
{
|
||||||
|
private final DCSCode mDCSCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param code that was detected
|
||||||
|
* @param timestamp when the code was detected
|
||||||
|
*/
|
||||||
|
public DCSMessage(DCSCode code, long timestamp)
|
||||||
|
{
|
||||||
|
super(timestamp);
|
||||||
|
mDCSCode = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "Digital Coded Squelch (DCS) Detected: " + mDCSCode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DCS code that was detected.
|
||||||
|
* @return code
|
||||||
|
*/
|
||||||
|
public DCSCode getDCSCode()
|
||||||
|
{
|
||||||
|
return mDCSCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return true; //We only send a message when the tone was valid
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Protocol getProtocol()
|
||||||
|
{
|
||||||
|
return Protocol.DCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.singletonList(new DCSIdentifier(mDCSCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.dcs;
|
||||||
|
|
||||||
|
import io.github.dsheirer.filter.Filter;
|
||||||
|
import io.github.dsheirer.filter.FilterElement;
|
||||||
|
import io.github.dsheirer.message.IMessage;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message filter for Digital Coded Squelch (DCS)
|
||||||
|
*/
|
||||||
|
public class DCSMessageFilter extends Filter<IMessage,String>
|
||||||
|
{
|
||||||
|
private static final String DCS_KEY = "DCS";
|
||||||
|
private final KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public DCSMessageFilter()
|
||||||
|
{
|
||||||
|
super("DCS Messages");
|
||||||
|
add(new FilterElement(DCS_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that this filter can process DCS messages.
|
||||||
|
* @param message to test
|
||||||
|
* @return true if the message can be processed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean canProcess(IMessage message)
|
||||||
|
{
|
||||||
|
return message instanceof DCSMessage && super.canProcess(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key extractor that always returns the same key constant.
|
||||||
|
* @return extractor function.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Function<IMessage, String> getKeyExtractor()
|
||||||
|
{
|
||||||
|
return mKeyExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key extractor
|
||||||
|
*/
|
||||||
|
private class KeyExtractor implements Function<IMessage,String>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public String apply(IMessage message)
|
||||||
|
{
|
||||||
|
return DCS_KEY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -84,6 +84,20 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
|
|||||||
setSampleRate(25000.0);
|
setSampleRate(25000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
super.start();
|
||||||
|
mQPSKDemodulator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
super.stop();
|
||||||
|
mQPSKDemodulator.stop();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR decoder type.
|
* DMR decoder type.
|
||||||
*/
|
*/
|
||||||
@@ -318,22 +332,4 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
|
|||||||
{
|
{
|
||||||
return DMRDecoder.this;
|
return DMRDecoder.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the decoder
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
//No-op
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the decoder
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void stop()
|
|
||||||
{
|
|
||||||
//No-op
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcem
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.VoteNowAdvice;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.VoteNowAdvice;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.ChannelGrant;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.ChannelGrant;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
||||||
@@ -65,25 +66,33 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoice
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||||
import io.github.dsheirer.util.PacketUtil;
|
import io.github.dsheirer.util.PacketUtil;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
|
||||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -102,7 +111,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
private DecodeEvent mCurrentCallEvent;
|
private DecodeEvent mCurrentCallEvent;
|
||||||
private long mCurrentFrequency;
|
private long mCurrentFrequency;
|
||||||
private boolean mIgnoreCRCChecksums;
|
private boolean mIgnoreCRCChecksums;
|
||||||
private Map<Integer,DecodeEvent> mDetectedCallEventsMap = new TreeMap<>();
|
private Map<DMRChannel,DecodeEvent> mDetectedCallEventsMap = new HashMap<>();
|
||||||
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
||||||
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
||||||
|
|
||||||
@@ -286,12 +295,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||||
|
|
||||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(sms.getTimestamp())
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
|
||||||
.eventType(DecodeEventType.SMS)
|
|
||||||
.eventDescription(DecodeEventType.SMS.toString())
|
|
||||||
.details("MESSAGE: " + sms.getSMS())
|
.details("MESSAGE: " + sms.getSMS())
|
||||||
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
||||||
.timeslot(sms.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
broadcast(smsEvent);
|
broadcast(smsEvent);
|
||||||
}
|
}
|
||||||
@@ -312,51 +319,124 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
mic.update(hyteraSmsPacket.getSource());
|
mic.update(hyteraSmsPacket.getSource());
|
||||||
mic.update(hyteraSmsPacket.getDestination());
|
mic.update(hyteraSmsPacket.getDestination());
|
||||||
|
|
||||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
|
||||||
.eventDescription(DecodeEventType.SMS.name())
|
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(packet.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details("SMS:" + hyteraSmsPacket.getSMS())
|
.details("SMS:" + hyteraSmsPacket.getSMS())
|
||||||
.build();
|
.build();
|
||||||
broadcast(smsEvent);
|
broadcast(smsEvent);
|
||||||
}
|
}
|
||||||
|
//Hytera Radio Registration Service (RRS)
|
||||||
|
else if(packet.getPacket() instanceof HyteraRrsPacket rrs)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("HYTERA RRS REGISTER RADIO:");
|
||||||
|
sb.append(rrs.getDestination());
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(sb.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
|
//Hytera Short Data
|
||||||
|
else if(packet.getPacket() instanceof HyteraShortDataPacket hsdp)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("HYTERA");
|
||||||
|
|
||||||
|
if(hsdp.getPacketSequence().isEncrypted())
|
||||||
|
{
|
||||||
|
HyteraDataEncryptionHeader hdeh = (HyteraDataEncryptionHeader)hsdp.getPacketSequence().getProprietaryDataHeader();
|
||||||
|
sb.append(" ENCRYPTED ALGORITHM:").append(hdeh.getAlgorithm());
|
||||||
|
sb.append(" KEY:").append(hdeh.getKeyId());
|
||||||
|
sb.append(" IV:").append(hdeh.getIV());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
|
||||||
|
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(sb.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
//Unknown Hytera Long Data Service Token Message
|
//Unknown Hytera Long Data Service Token Message
|
||||||
else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket)
|
else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket)
|
||||||
{
|
{
|
||||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
||||||
.eventDescription(DecodeEventType.UNKNOWN_PACKET.name())
|
|
||||||
.identifiers(mic)
|
.identifiers(mic)
|
||||||
.timeslot(packet.getTimeslot())
|
.timeslot(getTimeslot())
|
||||||
.details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
.details("HYTERA LONG DATA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||||
.build();
|
.build();
|
||||||
broadcast(unknownTokenEvent);
|
broadcast(unknownTokenEvent);
|
||||||
}
|
}
|
||||||
|
//Motorola ARS
|
||||||
|
else if(packet.getPacket() instanceof ARSPacket ars)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(ars.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
|
//Motorola LRRP
|
||||||
|
else if(packet.getPacket() instanceof LRRPPacket lrrp)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(lrrp.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
|
//Motorola XCMP
|
||||||
|
else if(packet.getPacket() instanceof XCMPPacket xcmp)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
|
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(xcmp.toString())
|
||||||
|
.build();
|
||||||
|
broadcast(shortDataEvent);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
||||||
.eventDescription(DecodeEventType.DATA_PACKET.name())
|
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
.timeslot(getTimeslot())
|
||||||
.timeslot(packet.getTimeslot())
|
|
||||||
.details(packet.toString())
|
.details(packet.toString())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
broadcast(packetEvent);
|
broadcast(packetEvent);
|
||||||
|
|
||||||
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
|
}
|
||||||
|
|
||||||
if (geoPosition != null) {
|
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
|
||||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp())
|
|
||||||
.channel(getCurrentChannel())
|
|
||||||
.eventDescription(DecodeEventType.GPS.toString())
|
|
||||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
|
||||||
.protocol(Protocol.LRRP)
|
|
||||||
.location(geoPosition)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
broadcast(plottableDecodeEvent);
|
if (geoPosition != null) {
|
||||||
}
|
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
|
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||||
|
.protocol(Protocol.LRRP)
|
||||||
|
.location(geoPosition)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
broadcast(plottableDecodeEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,6 +456,19 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(message.getSyncPattern() == DMRSyncPattern.BS_VOICE_FRAME_F && message instanceof VoiceEMBMessage voiceEmb)
|
||||||
|
{
|
||||||
|
if(voiceEmb.hasEmbeddedParameters())
|
||||||
|
{
|
||||||
|
EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters();
|
||||||
|
|
||||||
|
if(embedded.getShortBurst() instanceof EncryptionParameters arc4)
|
||||||
|
{
|
||||||
|
updateEncryptedCall(arc4, true, voiceEmb.getTimestamp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -505,14 +598,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD:
|
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD:
|
||||||
if(csbk instanceof Acknowledge)
|
if(csbk instanceof Acknowledge)
|
||||||
{
|
{
|
||||||
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE,
|
||||||
.eventDescription(DecodeEventType.RESPONSE.toString())
|
((Acknowledge) csbk).getReason().toString()));
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details(((Acknowledge)csbk).getReason().toString())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
broadcast(ackEvent);
|
|
||||||
}
|
}
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
@@ -520,14 +607,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC:
|
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC:
|
||||||
if(csbk instanceof Acknowledge)
|
if(csbk instanceof Acknowledge)
|
||||||
{
|
{
|
||||||
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE,
|
||||||
.eventDescription(DecodeEventType.RESPONSE.toString())
|
((Acknowledge) csbk).getReason().toString()));
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details(((Acknowledge)csbk).getReason().toString())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
broadcast(ackEvent);
|
|
||||||
}
|
}
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
@@ -537,33 +618,16 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
switch(((Ahoy)csbk).getServiceKind())
|
switch(((Ahoy)csbk).getServiceKind())
|
||||||
{
|
{
|
||||||
case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE:
|
case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE:
|
||||||
DecodeEvent registerEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, DecodeEventType.REGISTER.toString()));
|
||||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details(DecodeEventType.REGISTER.toString())
|
|
||||||
.build();
|
|
||||||
broadcast(registerEvent);
|
|
||||||
break;
|
break;
|
||||||
case CANCEL_CALL_SERVICE:
|
case CANCEL_CALL_SERVICE:
|
||||||
DecodeEvent cancelEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, "CANCEL CALL"));
|
||||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details("CANCEL CALL")
|
|
||||||
.build();
|
|
||||||
broadcast(cancelEvent);
|
|
||||||
break;
|
break;
|
||||||
case SUPPLEMENTARY_SERVICE:
|
case SUPPLEMENTARY_SERVICE:
|
||||||
if(csbk instanceof StunReviveKill)
|
if(csbk instanceof StunReviveKill)
|
||||||
{
|
{
|
||||||
DecodeEvent stunEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
|
||||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
((StunReviveKill)csbk).getCommand() + " RADIO"));
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details(((StunReviveKill)csbk).getCommand() + " RADIO")
|
|
||||||
.build();
|
|
||||||
broadcast(stunEvent);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FULL_DUPLEX_MS_TO_MS_PACKET_CALL_SERVICE:
|
case FULL_DUPLEX_MS_TO_MS_PACKET_CALL_SERVICE:
|
||||||
@@ -577,15 +641,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
if(csbk instanceof ServiceRadioCheck)
|
if(csbk instanceof ServiceRadioCheck)
|
||||||
{
|
{
|
||||||
ServiceRadioCheck src = (ServiceRadioCheck)csbk;
|
ServiceRadioCheck src = (ServiceRadioCheck)csbk;
|
||||||
|
broadcast(getDecodeEvent(csbk, DecodeEventType.RADIO_CHECK,
|
||||||
DecodeEvent checkEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
src.getServiceDescription() + " SERVICE FOR " +
|
||||||
.eventDescription(DecodeEventType.RADIO_CHECK.toString())
|
(src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO")));
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details(src.getServiceDescription() + " SERVICE FOR " +
|
|
||||||
(src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO"))
|
|
||||||
.build();
|
|
||||||
broadcast(checkEvent);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -599,14 +657,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
if(aloha.hasRadioIdentifier())
|
if(aloha.hasRadioIdentifier())
|
||||||
{
|
{
|
||||||
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge"));
|
||||||
.eventDescription(DecodeEventType.RESPONSE.toString())
|
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details("Aloha Acknowledge")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
broadcast(ackEvent);
|
|
||||||
resetState();
|
resetState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -618,24 +669,13 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
switch(((Announcement)csbk).getAnnouncementType())
|
switch(((Announcement)csbk).getAnnouncementType())
|
||||||
{
|
{
|
||||||
case MASS_REGISTRATION:
|
case MASS_REGISTRATION:
|
||||||
DecodeEvent massEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.REGISTER, "MASS REGISTRATION"));
|
||||||
.eventDescription(DecodeEventType.REGISTER.toString())
|
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details("MASS REGISTRATION")
|
|
||||||
.build();
|
|
||||||
broadcast(massEvent);
|
|
||||||
break;
|
break;
|
||||||
case VOTE_NOW_ADVICE:
|
case VOTE_NOW_ADVICE:
|
||||||
if(csbk instanceof VoteNowAdvice)
|
if(csbk instanceof VoteNowAdvice)
|
||||||
{
|
{
|
||||||
DecodeEvent voteEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
|
||||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
"VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode()));
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details("VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode())
|
|
||||||
.build();
|
|
||||||
broadcast(voteEvent);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -653,13 +693,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
case STANDARD_PROTECT:
|
case STANDARD_PROTECT:
|
||||||
if(csbk instanceof Protect)
|
if(csbk instanceof Protect)
|
||||||
{
|
{
|
||||||
DecodeEvent protectEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
|
||||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
"PROTECT: " + ((Protect)csbk).getProtectKind()));
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details("PROTECT: " + ((Protect)csbk).getProtectKind())
|
|
||||||
.build();
|
|
||||||
broadcast(protectEvent);
|
|
||||||
}
|
}
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
@@ -701,27 +736,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
ChannelGrant dataGrant = (ChannelGrant)csbk;
|
ChannelGrant dataGrant = (ChannelGrant)csbk;
|
||||||
DMRChannel channel = dataGrant.getChannel();
|
DMRChannel channel = dataGrant.getChannel();
|
||||||
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||||
csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
||||||
.channel(channel)
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
.details(csbk.getOpcode().getLabel())
|
mDetectedCallEventsMap.put(channel, event);
|
||||||
.eventDescription(DecodeEventType.DATA_CALL.toString())
|
|
||||||
.identifiers(mergedIdentifiers)
|
|
||||||
.timeslot(channel.getTimeslot())
|
|
||||||
.build();
|
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -740,27 +770,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
||||||
DMRChannel channel = tgGrant.getChannel();
|
DMRChannel channel = tgGrant.getChannel();
|
||||||
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
||||||
.channel(channel)
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
.details(csbk.getOpcode().getLabel())
|
mDetectedCallEventsMap.put(channel, event);
|
||||||
.eventDescription(DecodeEventType.CALL_GROUP.toString())
|
|
||||||
.identifiers(mergedIdentifiers)
|
|
||||||
.timeslot(channel.getTimeslot())
|
|
||||||
.build();
|
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -778,27 +803,23 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
ChannelGrant channelGrant = (ChannelGrant)csbk;
|
ChannelGrant channelGrant = (ChannelGrant)csbk;
|
||||||
DMRChannel channel = channelGrant.getChannel();
|
DMRChannel channel = channelGrant.getChannel();
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel,
|
||||||
.channel(channel)
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
.details(csbk.getOpcode().getLabel())
|
mDetectedCallEventsMap.put(channel, event);
|
||||||
.eventDescription(DecodeEventType.CALL_UNIT_TO_UNIT.toString())
|
|
||||||
.identifiers(mergedIdentifiers)
|
|
||||||
.timeslot(channel.getTimeslot())
|
|
||||||
.build();
|
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -817,15 +838,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
if(cmAloha.hasRadioIdentifier())
|
if(cmAloha.hasRadioIdentifier())
|
||||||
{
|
{
|
||||||
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge"));
|
||||||
.eventDescription(DecodeEventType.RESPONSE.toString())
|
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.details("Aloha Acknowledge")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
broadcast(ackEvent);
|
|
||||||
|
|
||||||
resetState();
|
resetState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -837,27 +850,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
|
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
|
||||||
DMRChannel channel = cpdcg.getChannel();
|
DMRChannel channel = cpdcg.getChannel();
|
||||||
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||||
csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
||||||
.channel(channel)
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
.details(csbk.getOpcode().getLabel())
|
mDetectedCallEventsMap.put(channel, event);
|
||||||
.eventDescription(DecodeEventType.DATA_CALL.toString())
|
|
||||||
.identifiers(mergedIdentifiers)
|
|
||||||
.timeslot(channel.getTimeslot())
|
|
||||||
.build();
|
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -871,21 +879,19 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
||||||
DecodeEvent event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
||||||
.details("Registration Request")
|
.details("Registration Request")
|
||||||
.eventDescription(DecodeEventType.REGISTER.toString())
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
.timeslot(getTimeslot())
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.build();
|
.build();
|
||||||
broadcast(event);
|
broadcast(event);
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
||||||
DecodeEvent regRespEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
||||||
.details("Registration Response")
|
.details("Registration Response")
|
||||||
.eventDescription(DecodeEventType.REGISTER.toString())
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
.timeslot(getTimeslot())
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.build();
|
.build();
|
||||||
broadcast(regRespEvent);
|
broadcast(regRespEvent);
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
@@ -896,27 +902,22 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
||||||
DMRChannel channel = cpvcu.getChannel();
|
DMRChannel channel = cpvcu.getChannel();
|
||||||
|
|
||||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
|
||||||
|
|
||||||
if(hasTrafficChannelManager())
|
if(hasTrafficChannelManager())
|
||||||
{
|
{
|
||||||
|
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||||
|
|
||||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||||
csbk.getTimestamp(), csbk.isEncrypted());
|
csbk.getTimestamp(), csbk.isEncrypted());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
|
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel);
|
||||||
|
|
||||||
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||||
{
|
{
|
||||||
detectedEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
||||||
.channel(channel)
|
new IdentifierCollection(csbk.getIdentifiers()));
|
||||||
.details(csbk.getOpcode().getLabel())
|
mDetectedCallEventsMap.put(channel, detectedEvent);
|
||||||
.eventDescription(DecodeEventType.CALL_GROUP.toString())
|
|
||||||
.identifiers(mergedIdentifiers)
|
|
||||||
.timeslot(channel.getTimeslot())
|
|
||||||
.build();
|
|
||||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -930,11 +931,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
break;
|
break;
|
||||||
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
||||||
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
||||||
.details("TALKGROUP AFFILIATION")
|
.details("TALKGROUP AFFILIATION")
|
||||||
.eventDescription(DecodeEventType.AFFILIATE.toString())
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
.timeslot(getTimeslot())
|
||||||
.timeslot(csbk.getTimeslot())
|
|
||||||
.build();
|
.build();
|
||||||
broadcast(affiliateEvent);
|
broadcast(affiliateEvent);
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||||
@@ -945,6 +945,24 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel,
|
||||||
|
IdentifierCollection identifierCollection) {
|
||||||
|
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||||
|
.channel(channel)
|
||||||
|
.details(csbk.getOpcode().getLabel())
|
||||||
|
.identifiers(identifierCollection)
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
|
||||||
|
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||||
|
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.details(details)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a copy of the current identifier collection, removes any USER class identifiers and loads the identifiers
|
* Creates a copy of the current identifier collection, removes any USER class identifiers and loads the identifiers
|
||||||
* argument values into the collection.
|
* argument values into the collection.
|
||||||
@@ -1043,6 +1061,15 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
switch(message.getOpcode())
|
switch(message.getOpcode())
|
||||||
{
|
{
|
||||||
|
case FULL_ENCRYPTION_PARAMETERS:
|
||||||
|
if(message instanceof io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters ep)
|
||||||
|
{
|
||||||
|
if(mCurrentCallEvent != null)
|
||||||
|
{
|
||||||
|
mCurrentCallEvent.setDetails(ep.getDetails());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION:
|
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION:
|
||||||
if(message instanceof CapacityPlusRestChannel)
|
if(message instanceof CapacityPlusRestChannel)
|
||||||
{
|
{
|
||||||
@@ -1066,11 +1093,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
|
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
|
||||||
if(message instanceof CapacityPlusGroupVoiceChannelUser cpgvcu)
|
if(message instanceof MotorolaGroupVoiceChannelUser cpgvcu)
|
||||||
{
|
{
|
||||||
updateRestChannel(cpgvcu.getRestChannel());
|
|
||||||
|
|
||||||
if(isTerminator)
|
if(isTerminator)
|
||||||
{
|
{
|
||||||
getIdentifierCollection().remove(Role.FROM);
|
getIdentifierCollection().remove(Role.FROM);
|
||||||
@@ -1185,20 +1210,17 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FULL_STANDARD_GPS_INFO:
|
case FULL_STANDARD_GPS_INFO:
|
||||||
if(message instanceof GPSInformation)
|
if(message instanceof GPSInformation gpsInformation)
|
||||||
{
|
{
|
||||||
GPSInformation gpsInformation = (GPSInformation)message;
|
PlottableDecodeEvent plottableGPS = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, message.getTimestamp())
|
||||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
.channel(getCurrentChannel())
|
||||||
ic.update(gpsInformation.getGPSLocation());
|
.details("LOCATION:" + gpsInformation.getGPSLocation())
|
||||||
|
.identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers()))
|
||||||
|
.protocol(Protocol.DMR)
|
||||||
|
.location(gpsInformation.getPosition())
|
||||||
|
.build();
|
||||||
|
|
||||||
DecodeEvent gpsEvent = DMRDecodeEvent.builder(message.getTimestamp())
|
broadcast(plottableGPS);
|
||||||
.eventDescription(DecodeEventType.GPS.toString())
|
|
||||||
.identifiers(ic)
|
|
||||||
.timeslot(message.getTimeslot())
|
|
||||||
.details("LOCATION:" + gpsInformation.getGPSLocation())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
broadcast(gpsEvent);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FULL_STANDARD_TALKER_ALIAS_COMPLETE:
|
case FULL_STANDARD_TALKER_ALIAS_COMPLETE:
|
||||||
@@ -1210,6 +1232,41 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current call with encryption information.
|
||||||
|
* @param encryptionParameters decoded from the Voice Frame F
|
||||||
|
* @param isGroup true for group or false for individual call.
|
||||||
|
*/
|
||||||
|
private void updateEncryptedCall(EncryptionParameters encryptionParameters, boolean isGroup, long timestamp)
|
||||||
|
{
|
||||||
|
if(mCurrentCallEvent != null)
|
||||||
|
{
|
||||||
|
String details = mCurrentCallEvent.getDetails();;
|
||||||
|
|
||||||
|
if(details == null)
|
||||||
|
{
|
||||||
|
details = encryptionParameters.toString();
|
||||||
|
}
|
||||||
|
else if(!details.contains(encryptionParameters.toString()) && !details.contains("ENCRYPTION"))
|
||||||
|
{
|
||||||
|
details += " " + encryptionParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentCallEvent.setDetails(details);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mCurrentCallEvent = DMRDecodeEvent.builder(isGroup ? DecodeEventType.CALL_GROUP_ENCRYPTED :
|
||||||
|
DecodeEventType.CALL_ENCRYPTED, timestamp)
|
||||||
|
.channel(getCurrentChannel())
|
||||||
|
.details(encryptionParameters.toString())
|
||||||
|
.identifiers(getIdentifierCollection().copyOf())
|
||||||
|
.timeslot(getTimeslot())
|
||||||
|
.build();
|
||||||
|
broadcast(mCurrentCallEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates or creates a current call event.
|
* Updates or creates a current call event.
|
||||||
*
|
*
|
||||||
@@ -1223,20 +1280,19 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
|
|
||||||
if(mCurrentCallEvent == null)
|
if(mCurrentCallEvent == null)
|
||||||
{
|
{
|
||||||
mCurrentCallEvent = DMRDecodeEvent.builder(timestamp)
|
mCurrentCallEvent = DMRDecodeEvent.builder(type, timestamp)
|
||||||
.channel(getCurrentChannel())
|
.channel(getCurrentChannel())
|
||||||
.eventDescription(type.toString())
|
|
||||||
.details(details)
|
.details(details)
|
||||||
.identifiers(getIdentifierCollection().copyOf())
|
.identifiers(getIdentifierCollection().copyOf())
|
||||||
|
.timeslot(getTimeslot())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
broadcast(mCurrentCallEvent);
|
broadcast(mCurrentCallEvent);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(type != DecodeEventType.CALL)
|
if(mCurrentCallEvent.getDetails() == null)
|
||||||
{
|
{
|
||||||
mCurrentCallEvent.setEventDescription(type.toString());
|
|
||||||
mCurrentCallEvent.setDetails(details);
|
mCurrentCallEvent.setDetails(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceSuperFrameProcessor;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -59,6 +61,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
||||||
private DecodeConfigDMR mConfigDMR;
|
private DecodeConfigDMR mConfigDMR;
|
||||||
|
private VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
|
||||||
|
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
|
||||||
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
||||||
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
||||||
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
||||||
@@ -89,19 +93,53 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
@Override
|
@Override
|
||||||
public void receive(IMessage message)
|
public void receive(IMessage message)
|
||||||
{
|
{
|
||||||
//Enrich messages that carry DMR Logical Slot Number channels with LCN to frequency mappings
|
if(message instanceof FullLCMessage flc)
|
||||||
|
{
|
||||||
|
if(flc.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor1.process(flc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor2.process(flc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(message instanceof VoiceMessage voiceMessage)
|
||||||
|
{
|
||||||
|
if(voiceMessage.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor1.process(voiceMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor2.process(voiceMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid())
|
||||||
|
{
|
||||||
|
if(dmrBurst.getTimeslot() == 1)
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor1.reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSuperFrameProcessor2.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings
|
||||||
if(message instanceof ITimeslotFrequencyReceiver)
|
if(message instanceof ITimeslotFrequencyReceiver)
|
||||||
{
|
{
|
||||||
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
||||||
int[] lsns = receiver.getLogicalTimeslotNumbers();
|
int[] lcns = receiver.getLogicalChannelNumbers();
|
||||||
|
|
||||||
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
||||||
|
|
||||||
for(int lsn: lsns)
|
for(int lcn: lcns)
|
||||||
{
|
{
|
||||||
if(mTimeslotFrequencyMap.containsKey(lsn))
|
if(mTimeslotFrequencyMap.containsKey(lcn))
|
||||||
{
|
{
|
||||||
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lsn));
|
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lcn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr;
|
package io.github.dsheirer.module.decode.dmr;
|
||||||
@@ -41,14 +38,13 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusCo
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusTrafficChannel;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusTrafficChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.Model;
|
import io.github.dsheirer.module.decode.dmr.message.type.Model;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the network configuration details of a DMR network from the broadcast messages
|
* Tracks the network configuration details of a DMR network from the broadcast messages
|
||||||
@@ -250,7 +246,7 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
ConnectPlusNeighborReport cpnr = (ConnectPlusNeighborReport)csbk;
|
ConnectPlusNeighborReport cpnr = (ConnectPlusNeighborReport)csbk;
|
||||||
mNeighborSites.addAll(cpnr.getNeighbors());
|
mNeighborSites.addAll(cpnr.getNeighbors());
|
||||||
}
|
}
|
||||||
if(mBrand == null)
|
if(mBrand == null || mBrand != BRAND_MOTOROLA_CONNECT_PLUS)
|
||||||
{
|
{
|
||||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||||
}
|
}
|
||||||
@@ -279,7 +275,7 @@ public class DMRNetworkConfigurationMonitor
|
|||||||
*/
|
*/
|
||||||
private void addDmrChannel(DMRChannel dmrChannel)
|
private void addDmrChannel(DMRChannel dmrChannel)
|
||||||
{
|
{
|
||||||
mObservedChannelMap.put(dmrChannel.getLogicalSlotNumber(), dmrChannel);
|
mObservedChannelMap.put(dmrChannel.getValue(), dmrChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset()
|
public void reset()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -55,9 +55,6 @@ import io.github.dsheirer.source.config.SourceConfigTuner;
|
|||||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.DisableChannelRotationMonitorRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.DisableChannelRotationMonitorRequest;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -66,6 +63,8 @@ import java.util.Objects;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitors channel grant and channel grant update messages to allocate traffic channels to capture
|
* Monitors channel grant and channel grant update messages to allocate traffic channels to capture
|
||||||
@@ -99,7 +98,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
private List<Channel> mManagedTrafficChannels;
|
private List<Channel> mManagedTrafficChannels;
|
||||||
|
|
||||||
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
|
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
|
||||||
private Map<Integer,DMRChannelGrantEvent> mLSNGrantEventMap = new ConcurrentHashMap<>();
|
private Map<DMRChannel,DMRChannelGrantEvent> mChannelGrantEventMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private Listener<ChannelEvent> mChannelEventListener;
|
private Listener<ChannelEvent> mChannelEventListener;
|
||||||
private Listener<IDecodeEvent> mDecodeEventListener;
|
private Listener<IDecodeEvent> mDecodeEventListener;
|
||||||
@@ -165,7 +164,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
{
|
{
|
||||||
for(int x = 0; x < maxTrafficChannels; x++)
|
for(int x = 0; x < maxTrafficChannels; x++)
|
||||||
{
|
{
|
||||||
Channel trafficChannel = new Channel("TRAFFIC", ChannelType.TRAFFIC);
|
Channel trafficChannel = new Channel("T-" + mParentChannel.getName(), ChannelType.TRAFFIC);
|
||||||
trafficChannel.setAliasListName(mParentChannel.getAliasListName());
|
trafficChannel.setAliasListName(mParentChannel.getAliasListName());
|
||||||
trafficChannel.setSystem(mParentChannel.getSystem());
|
trafficChannel.setSystem(mParentChannel.getSystem());
|
||||||
trafficChannel.setSite(mParentChannel.getSite());
|
trafficChannel.setSite(mParentChannel.getSite());
|
||||||
@@ -323,20 +322,18 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
|
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
|
||||||
Opcode opcode, long timestamp, boolean encrypted)
|
Opcode opcode, long timestamp, boolean encrypted)
|
||||||
{
|
{
|
||||||
int lsn = channel.getLogicalSlotNumber();
|
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||||
|
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
|
||||||
|
|
||||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||||
{
|
{
|
||||||
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
||||||
.channel(channel)
|
.channel(channel)
|
||||||
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString())
|
|
||||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||||
.identifiers(identifierCollection)
|
.identifiers(identifierCollection)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
mLSNGrantEventMap.put(lsn, event);
|
mChannelGrantEventMap.put(channel, event);
|
||||||
|
|
||||||
}
|
}
|
||||||
else //Update current event
|
else //Update current event
|
||||||
@@ -350,14 +347,13 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
{
|
{
|
||||||
event.end(timestamp);
|
event.end(timestamp);
|
||||||
|
|
||||||
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
||||||
.channel(channel)
|
.channel(channel)
|
||||||
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString() + " - Continue")
|
.details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
|
||||||
.identifiers(identifierCollection)
|
.identifiers(identifierCollection)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
mLSNGrantEventMap.put(lsn, event);
|
mChannelGrantEventMap.put(channel, event);
|
||||||
broadcast(event);
|
broadcast(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,15 +386,6 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
{
|
{
|
||||||
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
|
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
|
||||||
{
|
{
|
||||||
if(event.getEventDescription() == null)
|
|
||||||
{
|
|
||||||
event.setEventDescription(getEventType(opcode, identifierCollection, encrypted) + IGNORED);
|
|
||||||
}
|
|
||||||
else if(!event.getEventDescription().endsWith(IGNORED))
|
|
||||||
{
|
|
||||||
event.setEventDescription(event.getEventDescription() + IGNORED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(event.getDetails() == null)
|
if(event.getDetails() == null)
|
||||||
{
|
{
|
||||||
event.setDetails(DATA_CALL_IGNORED);
|
event.setDetails(DATA_CALL_IGNORED);
|
||||||
@@ -654,37 +641,37 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
|||||||
*/
|
*/
|
||||||
private void removeCallEvents(long frequency)
|
private void removeCallEvents(long frequency)
|
||||||
{
|
{
|
||||||
List<Integer> lsnsToRemove = new ArrayList<>();
|
List<DMRChannel> channelsToRemove = new ArrayList<>();
|
||||||
|
|
||||||
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
|
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||||
{
|
{
|
||||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
if(entry.getKey().getDownlinkFrequency() == frequency)
|
||||||
{
|
{
|
||||||
lsnsToRemove.add(entry.getKey());
|
channelsToRemove.add(entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Integer lsn: lsnsToRemove)
|
for(DMRChannel channel: channelsToRemove)
|
||||||
{
|
{
|
||||||
mLSNGrantEventMap.remove(lsn);
|
mChannelGrantEventMap.remove(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCallEventDetails(long frequency, String detailFragment)
|
private void updateCallEventDetails(long frequency, String detailFragment)
|
||||||
{
|
{
|
||||||
List<Integer> lsnsToUpdate = new ArrayList<>();
|
List<DMRChannel> channelsToUpdate = new ArrayList<>();
|
||||||
|
|
||||||
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
|
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||||
{
|
{
|
||||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
||||||
{
|
{
|
||||||
lsnsToUpdate.add(entry.getKey());
|
channelsToUpdate.add(entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Integer lsn: lsnsToUpdate)
|
for(DMRChannel channel: channelsToUpdate)
|
||||||
{
|
{
|
||||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||||
|
|
||||||
if(event != null)
|
if(event != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,7 +38,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.AbstractVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
@@ -105,63 +104,54 @@ public class DMRAudioModule extends AmbeAudioModule implements IdentifierUpdateP
|
|||||||
{
|
{
|
||||||
if(hasAudioCodec() && message.getTimeslot() == getTimeslot())
|
if(hasAudioCodec() && message.getTimeslot() == getTimeslot())
|
||||||
{
|
{
|
||||||
//DCDM doesn't provide FLCs or EMBs ... assume that the call is unencrypted.
|
//Attempt to set the audio encryption state from certain types of messages
|
||||||
if(!mEncryptedCallStateEstablished && message instanceof VoiceMessage vm &&
|
if(!mEncryptedCallStateEstablished)
|
||||||
vm.getSyncPattern().isDirectMode())
|
|
||||||
{
|
{
|
||||||
mEncryptedCallStateEstablished = true;
|
//DCDM doesn't provide FLCs or EMBs ... assume that the call is unencrypted.
|
||||||
mEncryptedCall = false;
|
if(message instanceof VoiceMessage vm && vm.getSyncPattern().isDirectMode())
|
||||||
List<byte[]> frames = vm.getAMBEFrames();
|
|
||||||
for(byte[] frame: frames)
|
|
||||||
{
|
{
|
||||||
processAudio(frame, message.getTimestamp());
|
mEncryptedCallStateEstablished = true;
|
||||||
|
mEncryptedCall = false;
|
||||||
}
|
}
|
||||||
}
|
//Both Motorola and Hytera signal their Basic Privacy (BP) scrambling in some of the Voice B-F frames
|
||||||
//Both Motorola and Hytera signal their Basic Privacy (BP) scrambling in some of the Voice B-F frames
|
//in the EMB field.
|
||||||
//in the EMB field.
|
else if(message instanceof VoiceEMBMessage voiceMessage && voiceMessage.getEMB().isValid())
|
||||||
else if(!mEncryptedCallStateEstablished && message instanceof VoiceEMBMessage voiceMessage)
|
|
||||||
{
|
|
||||||
if(voiceMessage.getEMB().isValid())
|
|
||||||
{
|
{
|
||||||
mEncryptedCallStateEstablished = true;
|
mEncryptedCallStateEstablished = true;
|
||||||
mEncryptedCall = voiceMessage.getEMB().isEncrypted();
|
mEncryptedCall = voiceMessage.getEMB().isEncrypted();
|
||||||
}
|
}
|
||||||
|
else if(message instanceof VoiceHeader voiceHeader)
|
||||||
|
{
|
||||||
|
LCMessage lc = voiceHeader.getLCMessage();
|
||||||
|
|
||||||
List<byte[]> frames = voiceMessage.getAMBEFrames();
|
if(lc instanceof FullLCMessage flc) //We know it is, but this null checks as well
|
||||||
for(byte[] frame: frames)
|
|
||||||
{
|
|
||||||
processAudio(frame, message.getTimestamp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(message instanceof VoiceMessage voiceMessage)
|
|
||||||
{
|
|
||||||
List<byte[]> frames = voiceMessage.getAMBEFrames();
|
|
||||||
for(byte[] frame: frames)
|
|
||||||
{
|
|
||||||
processAudio(frame, message.getTimestamp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(!mEncryptedCallStateEstablished && message instanceof VoiceHeader voiceHeader)
|
|
||||||
{
|
|
||||||
LCMessage lc = voiceHeader.getLCMessage();
|
|
||||||
|
|
||||||
if(lc instanceof FullLCMessage flc) //We know it is, but this null checks as well
|
|
||||||
{
|
|
||||||
if(flc.isValid())
|
|
||||||
{
|
{
|
||||||
mEncryptedCallStateEstablished = true;
|
if(flc.isValid())
|
||||||
mEncryptedCall = flc.isEncrypted();
|
{
|
||||||
|
mEncryptedCallStateEstablished = true;
|
||||||
|
mEncryptedCall = flc.isEncrypted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//Note: the DMRMessageProcessor extracts Full Link Control messages from Voice Frames B-C and sends them
|
||||||
|
// independent of any DMR Burst messaging. When encountered, it can be assumed that they are part of
|
||||||
|
// an ongoing call and can be used to establish encryption state when the FLC is a voice channel user.
|
||||||
|
else if(message instanceof AbstractVoiceChannelUser avcu)
|
||||||
|
{
|
||||||
|
mEncryptedCallStateEstablished = true;
|
||||||
|
mEncryptedCall = avcu.getServiceOptions().isEncrypted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//Note: the DMRMessageProcess extracts Full Link Control messages from Voice Frames B-C and sends them
|
|
||||||
// independent of any DMR Burst messaging. When encountered, it can be assumed that they are part of
|
//Queue or process audio frames. Note: audio frames are held/queued until encrypted state is established
|
||||||
// an ongoing call and can be used to establish encryption state when the FLC is a voice channel user.
|
//before any audio is generated for the audio segment.
|
||||||
else if(!mEncryptedCallStateEstablished && message instanceof AbstractVoiceChannelUser avcu)
|
if(message instanceof VoiceMessage voiceMessage)
|
||||||
{
|
{
|
||||||
ServiceOptions serviceOptions = avcu.getServiceOptions();
|
List<byte[]> frames = voiceMessage.getAMBEFrames();
|
||||||
mEncryptedCallStateEstablished = true;
|
for(byte[] frame: frames)
|
||||||
mEncryptedCall = serviceOptions.isEncrypted();
|
{
|
||||||
|
processAudio(frame, message.getTimestamp());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(message instanceof Terminator)
|
else if(message instanceof Terminator)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2021 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,17 +32,16 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChann
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR AMBE Frame recorder generates call sequence recordings containing JSON representations of audio
|
* DMR AMBE Frame recorder generates call sequence recordings containing JSON representations of audio
|
||||||
* frames, optional encryption and call identifiers.
|
* frames, optional encryption and call identifiers.
|
||||||
@@ -206,8 +205,8 @@ public class DMRCallSequenceRecorder extends MBECallSequenceRecorder
|
|||||||
{
|
{
|
||||||
switch(message.getOpcode())
|
switch(message.getOpcode())
|
||||||
{
|
{
|
||||||
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
|
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
|
||||||
if(message instanceof CapacityPlusGroupVoiceChannelUser cpvcu)
|
if(message instanceof MotorolaGroupVoiceChannelUser cpvcu)
|
||||||
{
|
{
|
||||||
if(mCallSequence == null)
|
if(mCallSequence == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.channel;
|
package io.github.dsheirer.module.decode.dmr.channel;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -26,6 +26,8 @@ import io.github.dsheirer.identifier.Role;
|
|||||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||||
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,8 +35,9 @@ import org.apache.commons.lang3.Validate;
|
|||||||
*
|
*
|
||||||
* Note: timeslots are tracked as 1 and 2
|
* Note: timeslots are tracked as 1 and 2
|
||||||
*/
|
*/
|
||||||
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor
|
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor, ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
|
private TimeslotFrequency mTimeslotFrequency;
|
||||||
private int mTimeslot;
|
private int mTimeslot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,13 +59,23 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repeater number for the channel.
|
* Repeater number aka Logical Channel Number (LCN) for the channel.
|
||||||
*/
|
*/
|
||||||
public int getRepeater()
|
public int getChannelNumber()
|
||||||
{
|
{
|
||||||
return getValue();
|
return getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to format the single channel number as an array for TimeslotFrequency matching.
|
||||||
|
* @return channel number as an integer array.
|
||||||
|
*/
|
||||||
|
public int[] getLogicalChannelNumbers()
|
||||||
|
{
|
||||||
|
return new int[]{getChannelNumber()};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timeslot for the channel.
|
* Timeslot for the channel.
|
||||||
* @return timeslot as zero-based index with values in range: 0 or 1.
|
* @return timeslot as zero-based index with values in range: 0 or 1.
|
||||||
@@ -72,36 +85,6 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
return mTimeslot;
|
return mTimeslot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Logical slot number for this channel. LSN is a 1-based index value where repeater one, timeslot 1 is
|
|
||||||
* LSN 1, timeslot 2 is LSN 2, etc.
|
|
||||||
*
|
|
||||||
* Formula: LSN = ((channel - 1) * 2) + timeslot
|
|
||||||
*
|
|
||||||
* @return logical slot number, a 1-based index value
|
|
||||||
*/
|
|
||||||
public int getLogicalSlotNumber()
|
|
||||||
{
|
|
||||||
int repeater = getRepeater();
|
|
||||||
|
|
||||||
if(repeater > 0)
|
|
||||||
{
|
|
||||||
return ((repeater - 1) * 2) + getTimeslot();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array of length 1 containing this channel's logical slot number
|
|
||||||
*/
|
|
||||||
public int[] getLSNArray()
|
|
||||||
{
|
|
||||||
int[] logicalSlotNumbers = new int[1];
|
|
||||||
logicalSlotNumbers[0] = getLogicalSlotNumber();
|
|
||||||
return logicalSlotNumbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of timeslots for the DMR channel.
|
* Number of timeslots for the DMR channel.
|
||||||
* @return 2 always.
|
* @return 2 always.
|
||||||
@@ -121,6 +104,13 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "CHAN:" + getChannelNumber() + ":" + getTimeslot();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Not implemented
|
* Not implemented
|
||||||
*/
|
*/
|
||||||
@@ -138,4 +128,72 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
|||||||
{
|
{
|
||||||
throw new IllegalArgumentException("This method is not supported");
|
throw new IllegalArgumentException("This method is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDownlinkFrequency()
|
||||||
|
{
|
||||||
|
if(mTimeslotFrequency != null)
|
||||||
|
{
|
||||||
|
return mTimeslotFrequency.getDownlinkFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getUplinkFrequency()
|
||||||
|
{
|
||||||
|
if(mTimeslotFrequency != null)
|
||||||
|
{
|
||||||
|
return mTimeslotFrequency.getUplinkFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the lsn to frequency mapper value.
|
||||||
|
* @param timeslotFrequency to set
|
||||||
|
*/
|
||||||
|
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
|
||||||
|
{
|
||||||
|
mTimeslotFrequency = timeslotFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||||
|
{
|
||||||
|
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
|
||||||
|
{
|
||||||
|
if(timeslotFrequency.getChannelNumber() == getChannelNumber())
|
||||||
|
{
|
||||||
|
setTimeslotFrequency(timeslotFrequency);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if(this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(!(o instanceof DMRChannel that))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!super.equals(o))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return getTimeslot() == that.getTimeslot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return Objects.hash(super.hashCode(), getTimeslot());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DMR logical channel. This channel uses a logical channel number and a timeslot.
|
|
||||||
*/
|
|
||||||
public class DMRLogicalChannel extends DMRChannel
|
|
||||||
{
|
|
||||||
private TimeslotFrequency mTimeslotFrequency;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
|
|
||||||
* calculated logical slot value for visual compatibility for users.
|
|
||||||
*
|
|
||||||
* @param channel number or repeater number
|
|
||||||
* @param logicalSlotNumber - zero based index.
|
|
||||||
*/
|
|
||||||
public DMRLogicalChannel(int channel, int timeslot)
|
|
||||||
{
|
|
||||||
super(channel, timeslot);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downlink frequency
|
|
||||||
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long getDownlinkFrequency()
|
|
||||||
{
|
|
||||||
if(mTimeslotFrequency != null)
|
|
||||||
{
|
|
||||||
return mTimeslotFrequency.getDownlinkFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uplink frequency
|
|
||||||
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public long getUplinkFrequency()
|
|
||||||
{
|
|
||||||
if(mTimeslotFrequency != null)
|
|
||||||
{
|
|
||||||
return mTimeslotFrequency.getUplinkFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the timeslot frequency mapping
|
|
||||||
* @param timeslotFrequency
|
|
||||||
*/
|
|
||||||
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
|
|
||||||
{
|
|
||||||
mTimeslotFrequency = timeslotFrequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formatted channel number
|
|
||||||
*/
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("LSN:").append(getLogicalSlotNumber());
|
|
||||||
// sb.append(" LCN:").append(getValue());
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Logical Slot Number (LSN) channel.
|
||||||
|
*
|
||||||
|
* Note: LSN 1-16 are represented in memory using the following channel and timeslot values:
|
||||||
|
*
|
||||||
|
* LSN: CHANNEL/TIMESLOT
|
||||||
|
* 1: 1/1
|
||||||
|
* 2: 1/2
|
||||||
|
* 3: 2/1
|
||||||
|
* 4: 2/2
|
||||||
|
* 5: 3/1
|
||||||
|
* 6: 3/2
|
||||||
|
* 7: 4/1
|
||||||
|
* 8: 4/2
|
||||||
|
* 9: 5/1
|
||||||
|
* 10: 5/2
|
||||||
|
* 11: 6/1
|
||||||
|
* 12: 6/2
|
||||||
|
* 13: 7/1
|
||||||
|
* 14: 7/2
|
||||||
|
* 15: 8/1
|
||||||
|
* 16: 8/2
|
||||||
|
*/
|
||||||
|
public class DMRLsn extends DMRChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param lsn in range 1 - 16
|
||||||
|
*/
|
||||||
|
public DMRLsn(int lsn)
|
||||||
|
{
|
||||||
|
super(((lsn - 1) / 2) + 1, ((lsn - 1) % 2) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "LSN:" + getLsn() + " LCN:" + getChannelNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logical Slot Number
|
||||||
|
* @return lsn
|
||||||
|
*/
|
||||||
|
public int getLsn()
|
||||||
|
{
|
||||||
|
return ((getChannelNumber() - 1) * 2) + getTimeslot();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,16 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.channel;
|
package io.github.dsheirer.module.decode.dmr.channel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Tier III Trunking Channel
|
* DMR Tier III Trunking channel. This channel uses a logical channel number and a timeslot.
|
||||||
*/
|
*/
|
||||||
public class DMRTier3Channel extends DMRLogicalChannel
|
public class DMRTier3Channel extends DMRChannel
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
|
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
|
||||||
* calculated logical slot value for visual compatibility for users.
|
* calculated logical slot value for visual compatibility for users.
|
||||||
*
|
*
|
||||||
* @param channel number or repeater number
|
* @param channel number or repeater number
|
||||||
* @param timeslot
|
* @param timeslot either 1 or 2.
|
||||||
*/
|
*/
|
||||||
public DMRTier3Channel(int channel, int timeslot)
|
public DMRTier3Channel(int channel, int timeslot)
|
||||||
{
|
{
|
||||||
@@ -18,23 +37,22 @@ public class DMRTier3Channel extends DMRLogicalChannel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical slot number for this channel.
|
* Tier III Channel ID
|
||||||
*
|
* @return channel ID
|
||||||
* Formula: LSN = (channel * 2) + timeslot
|
|
||||||
*
|
|
||||||
* @return logical slot number, a 1-based index value
|
|
||||||
*/
|
*/
|
||||||
@Override
|
public int getChannelId()
|
||||||
public int getLogicalSlotNumber()
|
|
||||||
{
|
{
|
||||||
int repeater = getRepeater();
|
return getChannelNumber() * 2 + getTimeslot();
|
||||||
|
|
||||||
if(repeater > 0)
|
|
||||||
{
|
|
||||||
return ((repeater) * 2) + getTimeslot();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted channel number
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(" LCN:").append(getChannelNumber());
|
||||||
|
sb.append(" CHANID:").append(getChannelId());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Rest Channel logical slot number (LSN).
|
||||||
|
*/
|
||||||
|
public class DmrRestLsn extends DMRLsn
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param lsn in range 1 - 16
|
||||||
|
*/
|
||||||
|
public DmrRestLsn(int lsn)
|
||||||
|
{
|
||||||
|
super(lsn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "REST:" + getLsn();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,9 +24,9 @@ import java.util.List;
|
|||||||
public interface ITimeslotFrequencyReceiver
|
public interface ITimeslotFrequencyReceiver
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Provides the logical slot number(s) that require a matching timeslot frequency mapping
|
* Provides the logical channel number(s) that require a matching timeslot frequency mapping
|
||||||
*/
|
*/
|
||||||
public int[] getLogicalTimeslotNumbers();
|
public int[] getLogicalChannelNumbers();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the list of timeslot frequency mappings to the implementer
|
* Applies the list of timeslot frequency mappings to the implementer
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -31,7 +31,7 @@ import javafx.beans.property.SimpleLongProperty;
|
|||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a timeslot number to a pair of channel frequency values
|
* Maps a logical channel number (LCN) to a pair of channel frequency values
|
||||||
*/
|
*/
|
||||||
public class TimeslotFrequency
|
public class TimeslotFrequency
|
||||||
{
|
{
|
||||||
@@ -73,7 +73,7 @@ public class TimeslotFrequency
|
|||||||
* Downlink Frequency property
|
* Downlink Frequency property
|
||||||
*/
|
*/
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public LongProperty downlinkFrequencyPropertyProperty()
|
public LongProperty downlinkFrequencyProperty()
|
||||||
{
|
{
|
||||||
return mDownlinkFrequencyProperty;
|
return mDownlinkFrequencyProperty;
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,10 @@ public class TimeslotFrequency
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical slot number (LSN) as a 1-index based counter
|
* Logical channel number (LCN) as a 1-index based counter
|
||||||
|
*
|
||||||
|
* Note: lsn (logical slot number) is a legacy name for this field. The field currently holds the LCN or logical
|
||||||
|
* channel number. To avoid breaking serialization, we leave this field labeled as lsn.
|
||||||
*/
|
*/
|
||||||
@JacksonXmlProperty(isAttribute = true, localName = "lsn")
|
@JacksonXmlProperty(isAttribute = true, localName = "lsn")
|
||||||
public int getNumber()
|
public int getNumber()
|
||||||
@@ -109,12 +112,52 @@ public class TimeslotFrequency
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the logical slot number (LSN) as a 1-index start
|
* Logical channel number which is the same value as getNumber.
|
||||||
* @param lsn where LSN 1 is the first slot, 2 the second, etc
|
* @return
|
||||||
*/
|
*/
|
||||||
public void setNumber(int lsn)
|
@JsonIgnore
|
||||||
|
public int getChannelNumber()
|
||||||
{
|
{
|
||||||
mNumberProperty.set(lsn);
|
return getNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public String getLogicalSlotNumbers()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
int channel = getChannelNumber();
|
||||||
|
|
||||||
|
if(channel > 0)
|
||||||
|
{
|
||||||
|
if(channel < 32) //Connect Plus uses 5-bits for LSN for a maximum value of 31
|
||||||
|
{
|
||||||
|
//Cap+ & Con+ Logical Slot Numbers
|
||||||
|
int lsn1 = (channel - 1) * 2 + 1;
|
||||||
|
int lsn2 = (channel - 1) * 2 + 2;
|
||||||
|
sb.append("LSN:").append(lsn1).append("/").append(lsn2).append(" or ");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Tier III channel IDs
|
||||||
|
int chanId1 = channel * 2 + 1;
|
||||||
|
int chanId2 = channel * 2 + 2;
|
||||||
|
sb.append("Tier3 Chan:").append(chanId1).append("/").append(chanId2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append("(empty)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the logical channel number (LCN) as a 1-index start
|
||||||
|
* @param lcn where LCN 1 is logical slot numbers (LSN) 1 and 2, LCN2=LSN3/4, etc.
|
||||||
|
*/
|
||||||
|
public void setNumber(int lcn)
|
||||||
|
{
|
||||||
|
mNumberProperty.set(lcn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,7 +203,7 @@ public class TimeslotFrequency
|
|||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "TIMESLOT LSN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
|
return "LCN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,7 +211,7 @@ public class TimeslotFrequency
|
|||||||
*/
|
*/
|
||||||
public static Callback<TimeslotFrequency,Observable[]> extractor()
|
public static Callback<TimeslotFrequency,Observable[]> extractor()
|
||||||
{
|
{
|
||||||
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyPropertyProperty(),
|
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyProperty(),
|
||||||
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
|
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,12 +14,13 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.dmr.event;
|
package io.github.dsheirer.module.decode.dmr.event;
|
||||||
|
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Channel Grant Event.
|
* DMR Channel Grant Event.
|
||||||
@@ -33,11 +33,12 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
|
* @param decodeEventType for the event
|
||||||
* @param timestamp for the event
|
* @param timestamp for the event
|
||||||
*/
|
*/
|
||||||
public DMRChannelGrantEvent(long timestamp)
|
public DMRChannelGrantEvent(DecodeEventType decodeEventType, long timestamp)
|
||||||
{
|
{
|
||||||
super(timestamp);
|
super(decodeEventType, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,9 +63,9 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
|||||||
* @param timeStart for the event
|
* @param timeStart for the event
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(long timeStart)
|
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(DecodeEventType decodeEventType, long timeStart)
|
||||||
{
|
{
|
||||||
return new DMRChannelGrantDecodeEventBuilder(timeStart);
|
return new DMRChannelGrantDecodeEventBuilder(decodeEventType, timeStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +75,7 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
|||||||
{
|
{
|
||||||
protected long mTimeStart;
|
protected long mTimeStart;
|
||||||
protected long mDuration;
|
protected long mDuration;
|
||||||
protected String mEventDescription;
|
protected DecodeEventType mDecodeEventType;
|
||||||
protected IdentifierCollection mIdentifierCollection;
|
protected IdentifierCollection mIdentifierCollection;
|
||||||
protected DMRChannel mChannel;
|
protected DMRChannel mChannel;
|
||||||
protected String mDetails;
|
protected String mDetails;
|
||||||
@@ -85,8 +86,9 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
|||||||
*
|
*
|
||||||
* @param timeStart
|
* @param timeStart
|
||||||
*/
|
*/
|
||||||
public DMRChannelGrantDecodeEventBuilder(long timeStart)
|
public DMRChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
|
||||||
{
|
{
|
||||||
|
mDecodeEventType = decodeEventType;
|
||||||
mTimeStart = timeStart;
|
mTimeStart = timeStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the channel descriptor for this event
|
* Sets the channel descriptor for this event
|
||||||
* @param channelDescriptor
|
* @param channel
|
||||||
*/
|
*/
|
||||||
public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel)
|
public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel)
|
||||||
{
|
{
|
||||||
@@ -129,16 +131,6 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the event description text
|
|
||||||
* @param description of the event
|
|
||||||
*/
|
|
||||||
public DMRChannelGrantDecodeEventBuilder eventDescription(String description)
|
|
||||||
{
|
|
||||||
mEventDescription = description;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the identifier collection.
|
* Sets the identifier collection.
|
||||||
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
|
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
|
||||||
@@ -165,12 +157,11 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
|||||||
*/
|
*/
|
||||||
public DMRChannelGrantEvent build()
|
public DMRChannelGrantEvent build()
|
||||||
{
|
{
|
||||||
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mTimeStart);
|
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mDecodeEventType, mTimeStart);
|
||||||
decodeEvent.setChannelDescriptor(mChannel);
|
decodeEvent.setChannelDescriptor(mChannel);
|
||||||
decodeEvent.setTimeslot(mChannel.getTimeslot());
|
decodeEvent.setTimeslot(mChannel.getTimeslot());
|
||||||
decodeEvent.setDetails(mDetails);
|
decodeEvent.setDetails(mDetails);
|
||||||
decodeEvent.setDuration(mDuration);
|
decodeEvent.setDuration(mDuration);
|
||||||
decodeEvent.setEventDescription(mEventDescription);
|
|
||||||
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
||||||
return decodeEvent;
|
return decodeEvent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,12 +14,13 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.event;
|
package io.github.dsheirer.module.decode.dmr.event;
|
||||||
|
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,9 +32,9 @@ public class DMRDecodeEvent extends DecodeEvent
|
|||||||
* Constucts a decode event
|
* Constucts a decode event
|
||||||
* @param start
|
* @param start
|
||||||
*/
|
*/
|
||||||
public DMRDecodeEvent(long start)
|
public DMRDecodeEvent(DecodeEventType decodeEventType, long start)
|
||||||
{
|
{
|
||||||
super(start);
|
super(decodeEventType, start);
|
||||||
setProtocol(Protocol.DMR);
|
setProtocol(Protocol.DMR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,9 +43,9 @@ public class DMRDecodeEvent extends DecodeEvent
|
|||||||
* @param timeStart for the event
|
* @param timeStart for the event
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
public static DecodeEventBuilder builder(long timeStart)
|
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
|
||||||
{
|
{
|
||||||
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
|
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
|
||||||
decodeEventBuilder.protocol(Protocol.DMR);
|
decodeEventBuilder.protocol(Protocol.DMR);
|
||||||
return decodeEventBuilder;
|
return decodeEventBuilder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.message;
|
package io.github.dsheirer.module.decode.dmr.message;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
/**
|
/**
|
||||||
@@ -53,4 +73,13 @@ public abstract class DMRBurst extends DMRMessage
|
|||||||
{
|
{
|
||||||
return mSyncPattern;
|
return mSyncPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the sync payload from between the two payload fragments.
|
||||||
|
* @return binary message containing just the sync payload bits (64).
|
||||||
|
*/
|
||||||
|
public BinaryMessage getSyncPayload()
|
||||||
|
{
|
||||||
|
return getMessage().get(SYNC_START, PAYLOAD_2_START);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message;
|
||||||
|
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR message that exposes a service options configuration
|
||||||
|
*/
|
||||||
|
public interface IServiceOptionsProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Service Options
|
||||||
|
* @return service options
|
||||||
|
*/
|
||||||
|
ServiceOptions getServiceOptions();
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -33,8 +33,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ConfirmedDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ConfirmedDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.DataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.DataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.DefinedShortDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.DefinedShortDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PiHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.RawShortDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.RawShortDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ResponseDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ResponseDataHeader;
|
||||||
@@ -43,9 +43,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.StatusDataHeader
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
|
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
|
||||||
@@ -98,7 +98,7 @@ public class DMRDataMessageFactory
|
|||||||
return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||||
case CHANNEL_CONTROL_ENC_HEADER:
|
case CHANNEL_CONTROL_ENC_HEADER:
|
||||||
case PI_HEADER:
|
case PI_HEADER:
|
||||||
return new HeaderMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
return new PiHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||||
case VOICE_HEADER:
|
case VOICE_HEADER:
|
||||||
return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
|
||||||
case DATA_ENC_HEADER:
|
case DATA_ENC_HEADER:
|
||||||
@@ -132,16 +132,16 @@ public class DMRDataMessageFactory
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MotorolaProprietaryDataHeader mprdh = new MotorolaProprietaryDataHeader(pattern,
|
MotorolaDataEncryptionHeader mprdh = new MotorolaDataEncryptionHeader(pattern,
|
||||||
payload, cach, slotType, timestamp, timeslot);
|
payload, cach, slotType, timestamp, timeslot);
|
||||||
mprdh.setValid(valid);
|
mprdh.setValid(valid);
|
||||||
return mprdh;
|
return mprdh;
|
||||||
}
|
}
|
||||||
case HYTERA_68:
|
case HYTERA_68:
|
||||||
HyteraProprietaryDataHeader hpdh = new HyteraProprietaryDataHeader(pattern, payload,
|
HyteraDataEncryptionHeader hsdh = new HyteraDataEncryptionHeader(pattern, payload,
|
||||||
cach, slotType, timestamp, timeslot);
|
cach, slotType, timestamp, timeslot);
|
||||||
hpdh.setValid(valid);
|
hsdh.setValid(valid);
|
||||||
return hpdh;
|
return hsdh;
|
||||||
default:
|
default:
|
||||||
ProprietaryDataHeader pdh = new ProprietaryDataHeader(pattern, payload, cach,
|
ProprietaryDataHeader pdh = new ProprietaryDataHeader(pattern, payload, cach,
|
||||||
slotType, timestamp, timeslot);
|
slotType, timestamp, timeslot);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public abstract class DataMessageWithLinkControl extends DataMessage
|
public abstract class DataMessageWithLinkControl extends DataMessage
|
||||||
{
|
{
|
||||||
private LCMessage mLCMessage;
|
protected LCMessage mLCMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ public class DataBlock1Rate extends DataBlock
|
|||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(hasRAS())
|
||||||
|
{
|
||||||
|
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||||
|
}
|
||||||
if(!isValid())
|
if(!isValid())
|
||||||
{
|
{
|
||||||
sb.append(" [CRC ERROR]");
|
sb.append(" [CRC ERROR]");
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ public class DataBlock1_2Rate extends DataBlock
|
|||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(hasRAS())
|
||||||
|
{
|
||||||
|
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||||
|
}
|
||||||
if(!isValid())
|
if(!isValid())
|
||||||
{
|
{
|
||||||
sb.append(" [CRC ERROR]");
|
sb.append(" [CRC ERROR]");
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user