mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
Compare commits
14 Commits
v0.6.0-alp
...
v0.6.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 19
|
||||
uses: actions/setup-java@v2
|
||||
- name: Set up JDK 20
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '19'
|
||||
java-version: '20'
|
||||
distribution: 'liberica'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Build with Gradle
|
||||
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: clean build
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</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_X" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</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
|
||||
18
build.gradle
18
build.gradle
@@ -53,12 +53,12 @@ repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.6.0-alpha6'
|
||||
version = '0.6.0-alpha7'
|
||||
|
||||
//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 {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(19)
|
||||
languageVersion = JavaLanguageVersion.of(20)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,16 +197,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.
|
||||
*/
|
||||
String jdk_base = '/home/denny/java_jdks/'
|
||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
|
||||
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
|
||||
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
|
||||
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
|
||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-20.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-20.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-20.0.1-full'
|
||||
|
||||
/**
|
||||
* 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_linux_aarch64 = jdk_download_base + "linux-aarch64" + 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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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
|
||||
* 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 float[] sHighPassFilterCoefficients;
|
||||
private final boolean mAudioFilterEnable;
|
||||
|
||||
static
|
||||
{
|
||||
@@ -70,27 +71,33 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
}
|
||||
}
|
||||
|
||||
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||
|
||||
/**
|
||||
* Creates an Audio Module.
|
||||
*
|
||||
* @param aliasList for aliasing identifiers
|
||||
* @param timeslot for this audio module
|
||||
* @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);
|
||||
mAudioFilterEnable = audioFilterEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
mAudioFilterEnable = audioFilterEnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,7 +128,11 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
||||
{
|
||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||
{
|
||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||
if(mAudioFilterEnable)
|
||||
{
|
||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||
}
|
||||
|
||||
addAudio(audioBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -24,9 +24,7 @@ import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
@@ -62,6 +60,11 @@ public class RdioScannerConfiguration extends BroadcastConfiguration
|
||||
mValid.unbind();
|
||||
mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
|
||||
Bindings.isNotNull(mHost)));
|
||||
|
||||
if(mHost.getValue() == null || mHost.getValue().isEmpty())
|
||||
{
|
||||
mHost.set("http://localhost");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -276,8 +276,7 @@ public class BinaryMessage extends BitSet
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BitSetFullException("bitset is full -- contains " +
|
||||
(mPointer + 1) + "bits");
|
||||
throw new BitSetFullException("bitset is full -- contains [" + mPointer + "/" + size() + "] bits");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,12 +132,10 @@ public enum SyncPattern
|
||||
true, true, false, false //1100 0xC
|
||||
}),
|
||||
|
||||
/* Sync (0x158) = 1101011000 */
|
||||
/* Sync (0x158) = 101011000 */
|
||||
PASSPORT( new boolean[]
|
||||
{
|
||||
true, //0001 0x1
|
||||
false, true, false, true, //0101 0x5
|
||||
true, false, false, false //1000 0x8
|
||||
true,false,true,false,true,true,false,false,false
|
||||
} ),
|
||||
|
||||
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.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
@@ -30,6 +32,7 @@ import java.util.Iterator;
|
||||
public class FloatNativeBuffer extends AbstractNativeBuffer
|
||||
{
|
||||
private float[] mInterleavedComplexSamples;
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -81,48 +84,51 @@ public class FloatNativeBuffer extends AbstractNativeBuffer
|
||||
|
||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||
{
|
||||
private boolean mEmpty;
|
||||
private int mBufferPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return !mEmpty;
|
||||
return mBufferPointer < mInterleavedComplexSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mEmpty)
|
||||
if(!hasNext())
|
||||
{
|
||||
throw new IllegalStateException("No more samples");
|
||||
}
|
||||
|
||||
mEmpty = true;
|
||||
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
|
||||
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
||||
mBufferPointer += BUFFER_SIZE * 2;
|
||||
|
||||
return SampleUtils.deinterleave(chunk, getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||
{
|
||||
private boolean mEmpty;
|
||||
|
||||
private int mBufferPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return !mEmpty;
|
||||
return mBufferPointer < mInterleavedComplexSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mEmpty)
|
||||
if(!hasNext())
|
||||
{
|
||||
throw new IllegalStateException("No more samples");
|
||||
}
|
||||
|
||||
mEmpty = true;
|
||||
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
|
||||
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
||||
mBufferPointer += BUFFER_SIZE * 2;
|
||||
|
||||
return new InterleavedComplexSamples(chunk, getTimestamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@ import javax.swing.JPanel;
|
||||
|
||||
public class NowPlayingPanel extends JPanel
|
||||
{
|
||||
private ChannelMetadataPanel mChannelMetadataPanel;
|
||||
private ChannelDetailPanel mChannelDetailPanel;
|
||||
private DecodeEventPanel mDecodeEventPanel;
|
||||
private MessageActivityPanel mMessageActivityPanel;
|
||||
private ChannelPowerPanel mChannelPowerPanel;
|
||||
private final ChannelMetadataPanel mChannelMetadataPanel;
|
||||
private final ChannelDetailPanel mChannelDetailPanel;
|
||||
private final DecodeEventPanel mDecodeEventPanel;
|
||||
private final MessageActivityPanel mMessageActivityPanel;
|
||||
private final ChannelPowerPanel mChannelPowerPanel;
|
||||
|
||||
/**
|
||||
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -16,61 +16,64 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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.dc.IIRSinglePoleDCRemovalFilter;
|
||||
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.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.Arrays;
|
||||
import org.slf4j.Logger;
|
||||
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
|
||||
* also employs a synchronization monitor that automatically adjusts symbol timing error gain according to the quantity
|
||||
* of successful message sync pattern detections. This is optimized for LTR signals that periodically transmit idle
|
||||
* 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.
|
||||
* This decoder monitors the incoming sample stream looking for either a positive or negative sequence of samples where
|
||||
* the energy delta across the baud period exceeds a threshold value and causes the symbol state to flip. The symbol
|
||||
* state remains across successive baud periods until the next state change sequence is detected.
|
||||
*
|
||||
* FM demodulated sample buffers should not be filtered prior to this decoder. An internal DC-removal filter coupled
|
||||
* with a low-pass filter removes any tuning offset and/or audio components above 300 Hertz. The DC removal filter is
|
||||
* 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.
|
||||
* Valid LTR messages start with a zero symbol and transition to a positive symbol for the sync bit, this decoder
|
||||
* monitors for excessively long sequences of a one symbol and forces the symbol state back to a zero.
|
||||
*/
|
||||
public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISyncStateListener
|
||||
public class LTRDecoder implements Listener<float[]>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
||||
|
||||
public static final float SAMPLES_PER_SYMBOL = 8000.0f / 300.0f;
|
||||
|
||||
protected static final float COARSE_TIMING_GAIN = 1.0f / 3.0f;
|
||||
protected static final float MEDIUM_TIMING_GAIN = 1.0f / 4.0f;
|
||||
protected static final float FINE_TIMING_GAIN = 1.0f / 5.0f;
|
||||
private static final Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
||||
//Slope calculation sumXX value is always the same for 27 evenly spaced samples
|
||||
private static final double SLOPE_CALCULATION_SUM_XX = 1637.999992057681;
|
||||
private static final float BAUD_LENGTH = 8000.0f / 300.0f;
|
||||
private static final float SLOPE_THRESHOLD = 0.0048f;
|
||||
private static final int MAX_ONES_SEQUENCE = 18;
|
||||
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 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
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(8000)
|
||||
.gridDensity(16)
|
||||
.oddLength(true)
|
||||
.passBandCutoff(300)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(500)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
||||
.build();
|
||||
.sampleRate(8000)
|
||||
.gridDensity(16)
|
||||
.oddLength(true)
|
||||
.passBandCutoff(300)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(500)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
||||
.build();
|
||||
|
||||
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 MessageFramer mMessageFramer;
|
||||
|
||||
private boolean mSampleDecision;
|
||||
|
||||
/**
|
||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder
|
||||
*
|
||||
* @param messageLength in symbols
|
||||
* Constructs an instance
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param messageLength in symbols
|
||||
* @param sampleBuffer to use for storing sample decisions
|
||||
* @param detector for symbol timing errors
|
||||
* Registers the listener to receive decoded symbols.
|
||||
* @param listener to register
|
||||
*/
|
||||
public LTRDecoder(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector)
|
||||
public void setListener(Listener<boolean[]> listener)
|
||||
{
|
||||
mSynchronizationMonitor = new SynchronizationMonitor(messageLength);
|
||||
mSynchronizationMonitor.setListener(this);
|
||||
mTimingErrorDetector = detector;
|
||||
mSampleBuffer = sampleBuffer;
|
||||
mSampleBuffer.setTimingGain(mSymbolTimingGain);
|
||||
mSymbolListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the ISyncDetectedListener interface to be notified of message sync detection events.
|
||||
*
|
||||
* This allows the internal timing error detector to adjust symbol timing error gain levels according to the
|
||||
* message synchronization state to quickly adjust to initial signal streams or to reduce gain levels once
|
||||
* synchronization has been achieved.
|
||||
* 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.
|
||||
*/
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
public void receive(float[] samples)
|
||||
{
|
||||
mSynchronizationMonitor.syncDetected(bitErrors);
|
||||
}
|
||||
|
||||
@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)
|
||||
if(mSymbolListener != null)
|
||||
{
|
||||
mSampleDecision = sample > 0.0;
|
||||
float[] filtered = mLowPassFilter.filter(samples);
|
||||
|
||||
mSampleBuffer.receive(mSampleDecision);
|
||||
mTimingErrorDetector.receive(mSampleDecision);
|
||||
int timingAdjust = 0;
|
||||
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());
|
||||
|
||||
mSynchronizationMonitor.increment();
|
||||
//Truncate the symbols array if we didn't fill it completely, due to symbol timing adjustments.
|
||||
if(symbolPointer != symbols.length)
|
||||
{
|
||||
mSymbolListener.receive(Arrays.copyOf(symbols, symbolPointer));
|
||||
}
|
||||
else
|
||||
{
|
||||
mSymbolListener.receive(symbols);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to receive decoded LTR symbols.
|
||||
*
|
||||
* @param messageFramer to receive symbols.
|
||||
* Calculates the slope of the 27 samples in the values array starting at the specified offset.
|
||||
* @param samples to load from
|
||||
* @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];
|
||||
|
||||
/**
|
||||
* Removes the symbol listener from receiving decoded LTR symbols.
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mMessageFramer = null;
|
||||
}
|
||||
double sumXY = 0;
|
||||
double fact1, dx, dy;
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
for(int x = 1; x < SLOPE_CALCULATION_LENGTH; x++)
|
||||
{
|
||||
case FINE:
|
||||
mSymbolTimingGain = FINE_TIMING_GAIN;
|
||||
break;
|
||||
case MEDIUM:
|
||||
mSymbolTimingGain = MEDIUM_TIMING_GAIN;
|
||||
break;
|
||||
case COARSE:
|
||||
mSymbolTimingGain = COARSE_TIMING_GAIN;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized sync state level: " + syncState.name());
|
||||
fact1 = 1.0f + x;
|
||||
dx = x - xbar;
|
||||
dy = samples[offset + x] - ybar;
|
||||
sumXY += dx * dy * (x / (1.0f + x));
|
||||
xbar += dx / fact1;
|
||||
ybar += dy / fact1;
|
||||
}
|
||||
|
||||
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
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.fsk;
|
||||
|
||||
public class LTRDecoderInstrumented extends 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),
|
||||
new ZeroCrossingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL));
|
||||
}
|
||||
|
||||
public SampleBufferInstrumented getSampleBuffer()
|
||||
{
|
||||
return (SampleBufferInstrumented)mSampleBuffer;
|
||||
}
|
||||
|
||||
public ZeroCrossingErrorDetectorInstrumented getErrorDetector()
|
||||
{
|
||||
return (ZeroCrossingErrorDetectorInstrumented)mTimingErrorDetector;
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -30,6 +30,7 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
||||
private IPhaseLockedLoop mPLL;
|
||||
private Complex mReceivedSample = new Complex(0, 0);
|
||||
private Listener<T> mSymbolListener;
|
||||
private boolean mRunning;
|
||||
|
||||
/**
|
||||
* Abstract Phase Shift Keyed (PSK) demodulator
|
||||
@@ -42,6 +43,31 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
||||
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
|
||||
*/
|
||||
@@ -90,7 +116,10 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
||||
|
||||
for(int x = 0; x < i.length; x++)
|
||||
{
|
||||
receive(i[x], q[x]);
|
||||
if(isRunning())
|
||||
{
|
||||
receive(i[x], q[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* 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/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.message.MessageDirection;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
@@ -98,6 +98,14 @@ public class CRCLTR
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
public class AllPassFilter<T> extends Filter<T, String>
|
||||
{
|
||||
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 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings( "unchecked" )
|
||||
public List getFilterElements()
|
||||
{
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
/**
|
||||
* Key extractor that always returns the same (String)key.
|
||||
*/
|
||||
public class AllPassKeyExtractor implements Function<T,String>
|
||||
{
|
||||
@Override
|
||||
public String apply(T t)
|
||||
{
|
||||
return KEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
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;
|
||||
protected boolean mEnabled = true;
|
||||
|
||||
public Filter( String name )
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Filter.class);
|
||||
private String mName;
|
||||
private Map<K, FilterElement<K>> mFilterElementMap = new HashMap<>();
|
||||
private IFilterChangeListener mFilterChangeListener;
|
||||
|
||||
/**
|
||||
* Indicates if this filter is enabled or disabled
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param name of this filter
|
||||
*/
|
||||
public Filter(String name)
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables (true) or disables (false) this filter. An enabled filter will
|
||||
* evaluate messages and a disabled filter will return false on all message
|
||||
* test methods.
|
||||
*/
|
||||
public void setEnabled( boolean enabled )
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of filter elements managed by this filter
|
||||
*/
|
||||
public abstract List<FilterElement<?>> getFilterElements();
|
||||
/**
|
||||
* Indicates if this filter contains a filter element matching the key value.
|
||||
* @param key to check
|
||||
* @return true if this filter has a matching filter element.
|
||||
*/
|
||||
protected boolean hasKey(K key)
|
||||
{
|
||||
return mFilterElementMap.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this filter
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName( String name )
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
/**
|
||||
* Key extractor function provided by the subclass.
|
||||
* @return key extractor function
|
||||
*/
|
||||
public abstract Function<T,K> getKeyExtractor();
|
||||
|
||||
@Override
|
||||
public boolean passes(T t)
|
||||
{
|
||||
K key = getKeyExtractor().apply(t);
|
||||
|
||||
if(mFilterElementMap.containsKey(key))
|
||||
{
|
||||
return mFilterElementMap.get(key).isEnabled();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.CellEditorListener;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeCellRenderer;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeCellEditor;
|
||||
import javax.swing.tree.TreeNode;
|
||||
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
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JTree mTree;
|
||||
private DefaultTreeModel mModel;
|
||||
private FilterSet<T> mFilterSet;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param filterSet to manage
|
||||
*/
|
||||
public FilterEditorPanel(FilterSet<T> filterSet)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
mFilterSet = filterSet;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the panel and the tree model.
|
||||
*/
|
||||
private void init()
|
||||
{
|
||||
DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet);
|
||||
|
||||
mModel = new DefaultTreeModel(root);
|
||||
|
||||
addFilterSet(mFilterSet, root);
|
||||
|
||||
mTree = new JTree(mModel);
|
||||
mTree.setShowsRootHandles(true);
|
||||
|
||||
mTree.addMouseListener(new MouseHandler());
|
||||
|
||||
mTree.getSelectionModel().setSelectionMode(
|
||||
TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
|
||||
mTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
mTree.setCellRenderer(new EditorTreeCellRenderer());
|
||||
|
||||
mTree.setCellEditor(new FilterTreeNodeEditor());
|
||||
|
||||
mTree.setCellEditor(new FilterTreeCellEditor());
|
||||
mTree.setEditable(true);
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
if(filter instanceof FilterSet childFilterSet)
|
||||
{
|
||||
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();
|
||||
|
||||
@@ -103,123 +131,39 @@ public class FilterEditorPanel<T> extends JPanel
|
||||
for(FilterElement<?> element : elements)
|
||||
{
|
||||
DefaultMutableTreeNode child = new DefaultMutableTreeNode(element);
|
||||
|
||||
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
|
||||
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)
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively sets the enabled state on all filter node children below the
|
||||
* specified parent node.
|
||||
* Recursively sets the enabled state on all filter node children below the specified parent node.
|
||||
*
|
||||
* @param model - model containing the tree nodes
|
||||
* @param node - parent node
|
||||
* @param enabled - true or false to enable or disable the filters
|
||||
*/
|
||||
private void setFilterEnabled(DefaultTreeModel model,
|
||||
DefaultMutableTreeNode node,
|
||||
boolean enabled)
|
||||
private void setFilterEnabled(DefaultTreeModel model, DefaultMutableTreeNode node, boolean enabled)
|
||||
{
|
||||
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 */
|
||||
Enumeration<?> children = node.children();
|
||||
@@ -228,43 +172,123 @@ public class FilterEditorPanel<T> extends JPanel
|
||||
{
|
||||
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
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public Component getTreeCellRendererComponent(JTree tree,
|
||||
Object treeNode,
|
||||
boolean selected,
|
||||
boolean expanded,
|
||||
boolean leaf,
|
||||
int row,
|
||||
boolean hasFocus)
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object treeNode, boolean selected, boolean expanded,
|
||||
boolean leaf, int row, boolean hasFocus)
|
||||
{
|
||||
if(treeNode instanceof DefaultMutableTreeNode)
|
||||
{
|
||||
Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject();
|
||||
|
||||
JCheckBox checkBox = null;
|
||||
|
||||
if(userObject instanceof IFilter)
|
||||
if(userObject instanceof IFilter filter)
|
||||
{
|
||||
IFilter<?> filter = (IFilter<?>) userObject;
|
||||
|
||||
checkBox = new JCheckBox(filter.getName());
|
||||
checkBox = new FilterCheckBox(filter);
|
||||
checkBox.setSelected(filter.isEnabled());
|
||||
}
|
||||
else if(userObject instanceof FilterElement)
|
||||
else if(userObject instanceof FilterElement element)
|
||||
{
|
||||
FilterElement<?> element = (FilterElement<?>) userObject;
|
||||
|
||||
checkBox = new JCheckBox(element.getName());
|
||||
checkBox = new FilterElementCheckBox(element);
|
||||
checkBox.setSelected(element.isEnabled());
|
||||
}
|
||||
|
||||
@@ -285,95 +309,110 @@ public class FilterEditorPanel<T> extends JPanel
|
||||
}
|
||||
}
|
||||
|
||||
return super.getTreeCellRendererComponent(tree, treeNode, selected,
|
||||
expanded, leaf, row, hasFocus);
|
||||
return super.getTreeCellRendererComponent(tree, treeNode, selected, expanded, leaf, row, hasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check box used in filter tree nodes
|
||||
*/
|
||||
public class FilterCheckBox extends JCheckBox
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private IFilter<?> mFilter;
|
||||
|
||||
private IFilter<T> mFilter;
|
||||
|
||||
public FilterCheckBox(IFilter<T> filter)
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param filter
|
||||
*/
|
||||
public FilterCheckBox(IFilter<?> filter)
|
||||
{
|
||||
super(filter.getName());
|
||||
|
||||
mFilter = filter;
|
||||
|
||||
setSelected(mFilter.isEnabled());
|
||||
addItemListener(e -> {
|
||||
DefaultMutableTreeNode selfNode = findFilterNode(getRoot(), mFilter);
|
||||
|
||||
addItemListener(new ItemListener()
|
||||
{
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e)
|
||||
if(selfNode != null)
|
||||
{
|
||||
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
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private FilterElement<?> mFilter;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param filter element
|
||||
*/
|
||||
public FilterElementCheckBox(FilterElement<?> filter)
|
||||
{
|
||||
super(filter.getName());
|
||||
|
||||
mFilter = filter;
|
||||
|
||||
setSelected(mFilter.isEnabled());
|
||||
|
||||
addItemListener(new ItemListener()
|
||||
{
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent arg0)
|
||||
addItemListener(arg0 -> {
|
||||
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
|
||||
DefaultMutableTreeNode self = findFilterElementNode(getRoot(), mFilter);
|
||||
if(self != null)
|
||||
{
|
||||
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
|
||||
updateParentage(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class FilterTreeNodeEditor implements TreeCellEditor
|
||||
/**
|
||||
* Editor for filter tree nodes
|
||||
*/
|
||||
public class FilterTreeCellEditor implements TreeCellEditor
|
||||
{
|
||||
@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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCellEditorListener(CellEditorListener l)
|
||||
{
|
||||
}
|
||||
public void addCellEditorListener(CellEditorListener l) {}
|
||||
|
||||
@Override
|
||||
public void cancelCellEditing()
|
||||
{
|
||||
|
||||
}
|
||||
public void cancelCellEditing() {}
|
||||
|
||||
@Override
|
||||
public Object getCellEditorValue()
|
||||
@@ -388,9 +427,7 @@ public class FilterEditorPanel<T> extends JPanel
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCellEditorListener(CellEditorListener l)
|
||||
{
|
||||
}
|
||||
public void removeCellEditorListener(CellEditorListener l) {}
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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>>
|
||||
{
|
||||
private boolean mEnabled;
|
||||
private T mElement;
|
||||
|
||||
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();
|
||||
}
|
||||
private boolean mEnabled;
|
||||
private final T mElement;
|
||||
private IFilterChangeListener mFilterChangeListener;
|
||||
|
||||
@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
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof FilterElement)) return false;
|
||||
return compareTo((FilterElement) o) == 0;
|
||||
public int compareTo(FilterElement<T> other)
|
||||
{
|
||||
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;
|
||||
|
||||
import io.github.dsheirer.log.LoggingSuppressor;
|
||||
import java.util.ArrayList;
|
||||
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>
|
||||
{
|
||||
protected List<IFilter<T>> mFilters = new ArrayList<IFilter<T>>();
|
||||
protected boolean mEnabled = true;
|
||||
protected String mName;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FilterSet.class);
|
||||
private static LoggingSuppressor sLoggingSuppressor = new LoggingSuppressor(LOGGER);
|
||||
private List<IFilter<T>> mFilters = new ArrayList<>();
|
||||
private String mName;
|
||||
private IFilterChangeListener mFilterChangeListener;
|
||||
|
||||
public FilterSet( String name )
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public FilterSet()
|
||||
{
|
||||
mName = "Message Filter";
|
||||
}
|
||||
|
||||
public FilterSet(IFilter<T> filter)
|
||||
/**
|
||||
* Constructs an instance of a filter set.
|
||||
*
|
||||
* @param name of this filter set.
|
||||
*/
|
||||
public FilterSet(String name)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
if( mEnabled )
|
||||
/**
|
||||
* Registers the listener with each child filter element.
|
||||
* @param listener to register
|
||||
*/
|
||||
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 true;
|
||||
}
|
||||
return filter.passes(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
{
|
||||
return 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 );
|
||||
return new ArrayList<>(mFilters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
/**
|
||||
* Adds a list of child filters to this filter set.
|
||||
* @param filters to add
|
||||
*/
|
||||
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
|
||||
public void setEnabled( boolean enabled )
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
public boolean isEnabled()
|
||||
{
|
||||
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;
|
||||
|
||||
/**
|
||||
* Filter interface
|
||||
*
|
||||
* @param <T> type of filter
|
||||
*/
|
||||
public interface IFilter<T>
|
||||
{
|
||||
/**
|
||||
* Generic filter method.
|
||||
*
|
||||
* @param message - message to filter
|
||||
* @return - true if the message passes the filter
|
||||
*/
|
||||
public boolean passes( T t );
|
||||
/**
|
||||
* Generic filter method.
|
||||
*
|
||||
* @param t - message to filter
|
||||
* @return - true if the message passes the filter
|
||||
*/
|
||||
boolean passes(T t);
|
||||
|
||||
/**
|
||||
* Indicates if the filter can process (filter) the object
|
||||
*
|
||||
* @param message - candidate message for filtering
|
||||
*
|
||||
* @return - true if the filter is capable of filtering the message
|
||||
*/
|
||||
public boolean canProcess( T t );
|
||||
/**
|
||||
* Indicates if the filter can process (filter) the object
|
||||
*
|
||||
* @param t - candidate message for filtering
|
||||
* @return - true if the filter is capable of filtering the message
|
||||
*/
|
||||
boolean canProcess(T t);
|
||||
|
||||
/**
|
||||
* Indicates if this filter is enabled to evaluate messages
|
||||
*/
|
||||
public boolean isEnabled();
|
||||
/**
|
||||
* Indicates if this filter is enabled to evaluate messages
|
||||
*/
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Sets the enabled state of the filter
|
||||
*/
|
||||
public void setEnabled( boolean enabled );
|
||||
|
||||
/**
|
||||
* Display name for the filter
|
||||
*/
|
||||
public String getName();
|
||||
/**
|
||||
* Display name for the filter
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* (Recursive) count of enabled child filter elements.
|
||||
*
|
||||
* @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-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,30 +14,36 @@
|
||||
*
|
||||
* 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.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.SyncLossMessage;
|
||||
import java.util.function.Function;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SyncLossMessageFilter extends Filter<IMessage>
|
||||
/**
|
||||
* Filter for sync-loss messages
|
||||
*/
|
||||
public class SyncLossMessageFilter extends Filter<IMessage,String>
|
||||
{
|
||||
private static final String SYNC_LOSS_KEY = "Sync-Loss";
|
||||
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public SyncLossMessageFilter()
|
||||
{
|
||||
super("Sync Loss");
|
||||
super("Sync Loss Messages");
|
||||
add(new FilterElement<>(SYNC_LOSS_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IMessage message)
|
||||
public Function<IMessage, String> getKeyExtractor()
|
||||
{
|
||||
return mEnabled && canProcess(message);
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,9 +52,20 @@ public class SyncLossMessageFilter extends Filter<IMessage>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||
@@ -58,7 +61,7 @@ public class LTRNetSampleBufferChart extends LineChart
|
||||
|
||||
mLTRNetDecoderInstrumented = decoder;
|
||||
decoder.bufferCount.addListener(new BufferChangeListener());
|
||||
decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
}
|
||||
|
||||
public void setBuffer(boolean[] buffer)
|
||||
@@ -89,7 +92,7 @@ public class LTRNetSampleBufferChart extends LineChart
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
|
||||
// setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
|
||||
|
||||
while(mSymbol.size() < 2)
|
||||
{
|
||||
@@ -115,29 +118,29 @@ public class LTRNetSampleBufferChart extends LineChart
|
||||
mSymbolSamples.add(sample);
|
||||
}
|
||||
|
||||
int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
|
||||
mSamplePoints.get(0).setXValue(pointer1);
|
||||
mSamplePoints.get(1).setXValue(pointer2);
|
||||
// mSamplePoints.get(0).setXValue(pointer1);
|
||||
// mSamplePoints.get(1).setXValue(pointer2);
|
||||
|
||||
float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
|
||||
float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
|
||||
// float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
|
||||
// float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
|
||||
|
||||
float start = pointer1 + samplesRemaining;
|
||||
float end = start + samplesPerSymbol;
|
||||
// float start = pointer1 + samplesRemaining;
|
||||
// float end = start + samplesPerSymbol;
|
||||
|
||||
mSymbolSamples.get(0).setXValue(start);
|
||||
mSymbolSamples.get(1).setXValue(end);
|
||||
// mSymbolSamples.get(0).setXValue(start);
|
||||
// mSymbolSamples.get(1).setXValue(end);
|
||||
|
||||
boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
|
||||
int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
|
||||
int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
|
||||
// boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
|
||||
// int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
|
||||
// int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
|
||||
|
||||
mSymbol.get(0).setXValue(symbolStart);
|
||||
mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
|
||||
mSymbol.get(1).setXValue(symbolEnd);
|
||||
mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
|
||||
// mSymbol.get(0).setXValue(symbolStart);
|
||||
// mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
|
||||
// mSymbol.get(1).setXValue(symbolEnd);
|
||||
// mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,20 +155,20 @@ public class LTRNetSampleBufferChart extends LineChart
|
||||
mZeroCrossing.add(sample);
|
||||
}
|
||||
|
||||
int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
|
||||
float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
|
||||
float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
|
||||
|
||||
int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
|
||||
reference--;
|
||||
|
||||
float start = reference - zeroCrossingIdeal;
|
||||
float end = reference - detectedZeroCrossing;
|
||||
|
||||
mZeroCrossing.get(0).setXValue(start);
|
||||
mZeroCrossing.get(1).setXValue(end);
|
||||
// float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
|
||||
// float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
|
||||
//
|
||||
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
|
||||
// reference--;
|
||||
//
|
||||
// float start = reference - zeroCrossingIdeal;
|
||||
// float end = reference - detectedZeroCrossing;
|
||||
//
|
||||
// mZeroCrossing.get(0).setXValue(start);
|
||||
// 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
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||
@@ -53,7 +56,7 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
||||
setData(observableList);
|
||||
|
||||
mLTRNetDecoderInstrumented = decoder;
|
||||
decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
decoder.bufferCount.addListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
@@ -84,13 +87,13 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
||||
|
||||
private void updateBuffer()
|
||||
{
|
||||
boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
Data<Number,Number> sample = mCurrentSamples.get(x);
|
||||
sample.setYValue(samples[x] ? 0.7f : 0.3f);
|
||||
}
|
||||
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
//
|
||||
// for(int x = 0; x < samples.length; x++)
|
||||
// {
|
||||
// Data<Number,Number> sample = mCurrentSamples.get(x);
|
||||
// sample.setYValue(samples[x] ? 0.7f : 0.3f);
|
||||
// }
|
||||
}
|
||||
|
||||
public class ErrorChangeListener implements ChangeListener
|
||||
@@ -98,35 +101,35 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
|
||||
|
||||
boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
Data<Number,Number> sample = mPreviousSamples.get(x);
|
||||
sample.setYValue(samples[x] ? -0.4f : -0.6f);
|
||||
}
|
||||
|
||||
Data<Number,Number> ideal1 = mIdeal.get(0);
|
||||
ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
Data<Number,Number> ideal2 = mIdeal.get(1);
|
||||
ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
|
||||
if(error == 0.0f)
|
||||
{
|
||||
Data<Number,Number> detected1 = mDetected.get(0);
|
||||
detected1.setXValue(0.0f);
|
||||
Data<Number,Number> detected2 = mDetected.get(1);
|
||||
detected2.setXValue(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Data<Number,Number> detected1 = mDetected.get(0);
|
||||
detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
Data<Number,Number> detected2 = mDetected.get(1);
|
||||
detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
}
|
||||
// float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
|
||||
//
|
||||
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
//
|
||||
// for(int x = 0; x < samples.length; x++)
|
||||
// {
|
||||
// Data<Number,Number> sample = mPreviousSamples.get(x);
|
||||
// sample.setYValue(samples[x] ? -0.4f : -0.6f);
|
||||
// }
|
||||
//
|
||||
// Data<Number,Number> ideal1 = mIdeal.get(0);
|
||||
// ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
// Data<Number,Number> ideal2 = mIdeal.get(1);
|
||||
// ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
//
|
||||
// if(error == 0.0f)
|
||||
// {
|
||||
// Data<Number,Number> detected1 = mDetected.get(0);
|
||||
// detected1.setXValue(0.0f);
|
||||
// Data<Number,Number> detected2 = mDetected.get(1);
|
||||
// detected2.setXValue(0.0f);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Data<Number,Number> detected1 = mDetected.get(0);
|
||||
// detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
// Data<Number,Number> detected2 = mDetected.get(1);
|
||||
// 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
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.LTRNetSampleBufferChart;
|
||||
@@ -142,8 +145,8 @@ public class LTRNetPane extends RealDecoderPane
|
||||
{
|
||||
if(mZeroCrossingErrorDetectorChart == null)
|
||||
{
|
||||
mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
|
||||
getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
|
||||
// mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
|
||||
// getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
|
||||
}
|
||||
|
||||
return mZeroCrossingErrorDetectorChart;
|
||||
|
||||
@@ -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.SourceConfigurationEditor;
|
||||
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.DecodeConfiguration;
|
||||
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.source.config.SourceConfiguration;
|
||||
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.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
@@ -58,15 +57,15 @@ import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.controlsfx.control.SegmentedButton;
|
||||
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
|
||||
*/
|
||||
public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(NBFMConfigurationEditor.class);
|
||||
private TitledPane mAuxDecoderPane;
|
||||
private TitledPane mDecoderPane;
|
||||
private TitledPane mEventLogPane;
|
||||
@@ -75,8 +74,9 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
private TextField mTalkgroupField;
|
||||
private TextField mSquelchThresholdField;
|
||||
private ToggleSwitch mSquelchAutoTrackSwitch;
|
||||
private ToggleSwitch mAudioFilterEnable;
|
||||
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);
|
||||
private ToggleSwitch mBasebandRecordSwitch;
|
||||
private SegmentedButton mBandwidthButton;
|
||||
@@ -84,9 +84,9 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
private SourceConfigurationEditor mSourceConfigurationEditor;
|
||||
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
|
||||
private EventLogConfigurationEditor mEventLogConfigurationEditor;
|
||||
private TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
|
||||
private IntegerFormatter mDecimalFormatter = new IntegerFormatter(1, 65535);
|
||||
private HexFormatter mHexFormatter = new HexFormatter(1, 65535);
|
||||
private final TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
|
||||
private final IntegerFormatter mDecimalFormatter = new IntegerFormatter(1, 65535);
|
||||
private final HexFormatter mHexFormatter = new HexFormatter(1, 65535);
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -162,12 +162,15 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
|
||||
Label talkgroupLabel = new Label("Talkgroup To Assign");
|
||||
GridPane.setHalignment(talkgroupLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(talkgroupLabel, 6, 0);
|
||||
GridPane.setConstraints(talkgroupLabel, 0, 1);
|
||||
gridPane.getChildren().add(talkgroupLabel);
|
||||
|
||||
GridPane.setConstraints(getTalkgroupField(), 7, 0);
|
||||
GridPane.setConstraints(getTalkgroupField(), 1, 1);
|
||||
gridPane.getChildren().add(getTalkgroupField());
|
||||
|
||||
GridPane.setConstraints(getAudioFilterEnable(), 2, 1);
|
||||
gridPane.getChildren().add(getAudioFilterEnable());
|
||||
|
||||
mDecoderPane.setContent(gridPane);
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
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
|
||||
//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.
|
||||
mBandwidthButton.getToggleGroup().getToggles().addListener(new ListChangeListener<Toggle>()
|
||||
mBandwidthButton.getToggleGroup().getToggles().addListener((ListChangeListener<Toggle>)c ->
|
||||
{
|
||||
@Override
|
||||
public void onChanged(Change<? extends Toggle> c)
|
||||
//This change event happens when the toggles are added -- we don't need to inspect the change event
|
||||
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
|
||||
if(getItem() != null && getItem().getDecodeConfiguration() instanceof DecodeConfigNBFM)
|
||||
//Capture current modified state so that we can reapply after adjusting control states
|
||||
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
|
||||
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);
|
||||
bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
|
||||
}
|
||||
|
||||
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());
|
||||
getSquelchAutoTrackSwitch().setDisable(false);
|
||||
getSquelchAutoTrackSwitch().setSelected(decodeConfigNBFM.isSquelchAutoTrack());
|
||||
getAudioFilterEnable().setDisable(false);
|
||||
getAudioFilterEnable().setSelected(decodeConfigNBFM.isAudioFilter());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -464,6 +481,8 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
getSquelchThresholdField().setDisable(true);
|
||||
getSquelchAutoTrackSwitch().setDisable(true);
|
||||
getSquelchAutoTrackSwitch().setSelected(false);
|
||||
getAudioFilterEnable().setDisable(true);
|
||||
getAudioFilterEnable().setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +519,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
config.setTalkgroup(talkgroup);
|
||||
config.setSquelchThreshold(mSquelchTextFormatter.getValue());
|
||||
config.setSquelchAutoTrack(getSquelchAutoTrackSwitch().isSelected());
|
||||
config.setAudioFilter(getAudioFilterEnable().isSelected());
|
||||
getItem().setDecodeConfiguration(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,21 +20,16 @@
|
||||
package io.github.dsheirer.gui.playlist.streaming;
|
||||
|
||||
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.gui.control.IntegerTextField;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import javafx.geometry.HPos;
|
||||
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.TextField;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
/**
|
||||
* RdioScanner calls API configuration editor
|
||||
@@ -42,6 +37,7 @@ import org.controlsfx.control.ToggleSwitch;
|
||||
public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfiguration>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerEditor.class);
|
||||
private static final String API_PATH = "/api/call-upload";
|
||||
private IntegerTextField mSystemIdTextField;
|
||||
private IntegerTextField mMaxAgeTextField;
|
||||
private TextField mApiKeyTextField;
|
||||
@@ -71,7 +67,14 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
|
||||
{
|
||||
getSystemIdTextField().set(item.getSystemID());
|
||||
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));
|
||||
}
|
||||
else
|
||||
@@ -97,7 +100,13 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
|
||||
{
|
||||
int systemID = getSystemIdTextField().get() != null ? getSystemIdTextField().get() : 0;
|
||||
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().setMaximumRecordingAge(getMaxAgeTextField().get() * 1000);
|
||||
}
|
||||
@@ -169,6 +178,11 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
|
||||
GridPane.setConstraints(getHostTextField(), 1, row);
|
||||
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)");
|
||||
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(maxAgeLabel, 0, ++row);
|
||||
|
||||
@@ -17,28 +17,32 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
// Generated by jextract
|
||||
package io.github.dsheirer.identifier.dcs;
|
||||
|
||||
package io.github.dsheirer.source.tuner.sdrplay.api.v3_08;
|
||||
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;
|
||||
|
||||
import java.lang.foreign.FunctionDescriptor;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
class constants$10 {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
static final FunctionDescriptor sdrplay_api_SwapRspDuoMode$FUNC = FunctionDescriptor.of(Constants$root.C_LONG$LAYOUT,
|
||||
Constants$root.C_POINTER$LAYOUT,
|
||||
Constants$root.C_POINTER$LAYOUT,
|
||||
Constants$root.C_LONG$LAYOUT,
|
||||
Constants$root.C_DOUBLE$LAYOUT,
|
||||
Constants$root.C_LONG$LAYOUT,
|
||||
Constants$root.C_LONG$LAYOUT,
|
||||
Constants$root.C_LONG$LAYOUT,
|
||||
Constants$root.C_LONG$LAYOUT
|
||||
);
|
||||
static final MethodHandle sdrplay_api_SwapRspDuoMode$MH = RuntimeHelper.downcallHandle(
|
||||
"sdrplay_api_SwapRspDuoMode",
|
||||
constants$10.sdrplay_api_SwapRspDuoMode$FUNC
|
||||
);
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return Protocol.DCS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,7 +21,6 @@ package io.github.dsheirer.module;
|
||||
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -50,7 +49,7 @@ public abstract class HistoryModule<T> extends Module implements Listener<T>
|
||||
*/
|
||||
public List<T> getItems()
|
||||
{
|
||||
return new ArrayList<>(mItems);
|
||||
return new ArrayList<>(mItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -117,8 +117,8 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
||||
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
|
||||
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
|
||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
|
||||
private MessageHistory mMessageHistory = new MessageHistory(500);
|
||||
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(200);
|
||||
private MessageHistory mMessageHistory = new MessageHistory(200);
|
||||
private AbstractChannelState mChannelState;
|
||||
private EventBus mEventBus;
|
||||
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
|
||||
* 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.sample.Listener;
|
||||
|
||||
/**
|
||||
* Base decoder class.
|
||||
*/
|
||||
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();
|
||||
protected Listener<IMessage> mMessageListener;
|
||||
private boolean mRunning;
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
//no-op
|
||||
mRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this decoder and sets the running flag to false.
|
||||
*/
|
||||
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()
|
||||
|
||||
@@ -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.config.AuxDecodeConfiguration;
|
||||
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.DMRDecoderState;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
|
||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||
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.Fleetsync2DecoderState;
|
||||
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.P25P1DecoderLSM;
|
||||
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.P25P2DecoderHDQPSK;
|
||||
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.PassportDecoder;
|
||||
import io.github.dsheirer.module.decode.passport.PassportDecoderState;
|
||||
import io.github.dsheirer.module.decode.passport.PassportMessageFilter;
|
||||
import io.github.dsheirer.module.decode.tait.Tait1200Decoder;
|
||||
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.demodulate.fm.FMDemodulatorModule;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
@@ -98,12 +104,14 @@ import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for creating decoder modules to use in a processing chain for any supported protocols.
|
||||
*/
|
||||
public class DecoderFactory
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DecoderFactory.class);
|
||||
|
||||
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
|
||||
@@ -179,6 +187,13 @@ public class DecoderFactory
|
||||
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) {
|
||||
modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration()));
|
||||
|
||||
@@ -188,6 +203,13 @@ public class DecoderFactory
|
||||
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) {
|
||||
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) {
|
||||
modules.add(new PassportDecoder(decodeConfig));
|
||||
modules.add(new PassportDecoderState());
|
||||
modules.add(new AudioModule(aliasList));
|
||||
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
|
||||
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) {
|
||||
DecodeConfigMPT1327 mptConfig = decodeConfig;
|
||||
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
|
||||
// actual call timeout.
|
||||
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();
|
||||
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)
|
||||
@@ -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) {
|
||||
modules.add(new LTRNetDecoder(decodeConfig));
|
||||
modules.add(new LTRNetDecoderState());
|
||||
modules.add(new AudioModule(aliasList));
|
||||
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
|
||||
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) {
|
||||
MessageDirection direction = decodeConfig.getMessageDirection();
|
||||
modules.add(new LTRStandardDecoder(null, direction));
|
||||
modules.add(new LTRStandardDecoder(direction));
|
||||
modules.add(new LTRStandardDecoderState());
|
||||
modules.add(new AudioModule(aliasList));
|
||||
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
|
||||
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)
|
||||
{
|
||||
if(!(decodeConfig instanceof DecodeConfigNBFM))
|
||||
@@ -312,9 +371,16 @@ public class DecoderFactory
|
||||
DecodeConfigNBFM decodeConfigNBFM = (DecodeConfigNBFM)decodeConfig;
|
||||
modules.add(new NBFMDecoder(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) {
|
||||
|
||||
if(!(decodeConfig instanceof DecodeConfigAM))
|
||||
@@ -326,7 +392,7 @@ public class DecoderFactory
|
||||
DecodeConfigAM decodeConfigAM = (DecodeConfigAM) decodeConfig;
|
||||
modules.add(new AMDecoder(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
|
||||
* @return - list of auxiliary decoders
|
||||
@@ -396,6 +462,10 @@ public class DecoderFactory
|
||||
{
|
||||
switch(auxDecoder)
|
||||
{
|
||||
case DCS:
|
||||
modules.add(new DCSDecoder());
|
||||
modules.add(new DCSDecoderState());
|
||||
break;
|
||||
case FLEETSYNC2:
|
||||
modules.add(new Fleetsync2Decoder());
|
||||
modules.add(new Fleetsync2DecoderState());
|
||||
@@ -427,7 +497,7 @@ public class DecoderFactory
|
||||
*/
|
||||
public static FilterSet<IMessage> getMessageFilters(List<Module> modules)
|
||||
{
|
||||
FilterSet<IMessage> filterSet = new FilterSet<>();
|
||||
FilterSet<IMessage> filterSet = new FilterSet<>("Message Filters");
|
||||
|
||||
for(Module module : modules)
|
||||
{
|
||||
@@ -437,25 +507,28 @@ public class DecoderFactory
|
||||
}
|
||||
}
|
||||
|
||||
/* If we don't have any filters, add an ALL-PASS filter */
|
||||
if(filterSet.getFilters().isEmpty())
|
||||
{
|
||||
filterSet.addFilter(new AllPassFilter<>());
|
||||
}
|
||||
//Add an all-others filter as a catch-all for anything that isn't handled by the decoder filters.
|
||||
filterSet.addFilter(new AllPassFilter<>("All Other Messages Filter"));
|
||||
|
||||
return filterSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
ArrayList<IFilter<IMessage>> filters = new ArrayList<>();
|
||||
List<IFilter<IMessage>> filters = new ArrayList<>();
|
||||
|
||||
switch(decoder)
|
||||
{
|
||||
case DCS:
|
||||
filters.add(new DCSMessageFilter());
|
||||
break;
|
||||
case DMR:
|
||||
filters.add(new DmrMessageFilterSet());
|
||||
break;
|
||||
case FLEETSYNC2:
|
||||
filters.add(new FleetsyncMessageFilter());
|
||||
break;
|
||||
@@ -475,13 +548,16 @@ public class DecoderFactory
|
||||
filters.add(new MPT1327MessageFilter());
|
||||
break;
|
||||
case P25_PHASE1:
|
||||
filters.add(new P25MessageFilterSet());
|
||||
filters.add(new P25P1MessageFilterSet());
|
||||
break;
|
||||
case P25_PHASE2:
|
||||
filters.add(new P25P2MessageFilterSet());
|
||||
break;
|
||||
case PASSPORT:
|
||||
filters.add(new PassportMessageFilter());
|
||||
break;
|
||||
case DMR:
|
||||
//filters.add(new DMR) //todo: not finished
|
||||
case TAIT_1200:
|
||||
filters.add(new Tait1200MessageFilter());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -518,7 +594,7 @@ public class DecoderFactory
|
||||
case P25_PHASE2:
|
||||
return new DecodeConfigP25Phase2();
|
||||
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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,7 +19,6 @@
|
||||
package io.github.dsheirer.module.decode;
|
||||
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
@@ -39,6 +38,7 @@ public enum DecoderType
|
||||
P25_PHASE2("P25 Phase 2", "P25-2", Protocol.APCO25_PHASE2),
|
||||
|
||||
//Auxiliary Decoders
|
||||
DCS("Digital Coded Squelch (DCS)", "DCS", Protocol.DCS),
|
||||
FLEETSYNC2("Fleetsync II", "Fleetsync2", Protocol.FLEETSYNC),
|
||||
LJ_1200("LJ1200 173.075", "LJ1200", Protocol.LOJACK),
|
||||
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
|
||||
*/
|
||||
public static final EnumSet<DecoderType> AUX_DECODERS =
|
||||
EnumSet.of(DecoderType.FLEETSYNC2,
|
||||
EnumSet.of(DecoderType.DCS,
|
||||
DecoderType.FLEETSYNC2,
|
||||
DecoderType.LJ_1200,
|
||||
DecoderType.MDC1200,
|
||||
DecoderType.TAIT_1200);
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
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.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
@@ -100,9 +101,8 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
|
||||
|
||||
if(mDecodeEvent == null)
|
||||
{
|
||||
mDecodeEvent = DecodeEvent.builder(System.currentTimeMillis())
|
||||
mDecodeEvent = DecodeEvent.builder(DecodeEventType.CALL, System.currentTimeMillis())
|
||||
.channel(mChannelDescriptor)
|
||||
.eventDescription("CALL")
|
||||
.details(getDecoderType().name())
|
||||
.identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers()))
|
||||
.build();
|
||||
@@ -121,6 +121,7 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
|
||||
startCallEvent();
|
||||
}
|
||||
|
||||
getIdentifierCollection().update(getTalkgroupIdentifier());
|
||||
mDecodeEvent.update(System.currentTimeMillis());
|
||||
broadcast(mDecodeEvent);
|
||||
}
|
||||
|
||||
281
src/main/java/io/github/dsheirer/module/decode/dcs/DCSCode.java
Normal file
281
src/main/java/io/github/dsheirer/module/decode/dcs/DCSCode.java
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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
|
||||
*/
|
||||
private static final EnumSet<DCSCode> INVERTED_CODES = EnumSet.range(I023, I754);
|
||||
|
||||
/**
|
||||
* 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.name();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
mQPSKDemodulator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
super.stop();
|
||||
mQPSKDemodulator.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* DMR decoder type.
|
||||
*/
|
||||
@@ -318,22 +332,4 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
|
||||
{
|
||||
return DMRDecoder.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the decoder
|
||||
*/
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the decoder
|
||||
*/
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,9 +286,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(sms.getTimestamp())
|
||||
.eventType(DecodeEventType.SMS)
|
||||
.eventDescription(DecodeEventType.SMS.toString())
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
|
||||
.details("MESSAGE: " + sms.getSMS())
|
||||
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
||||
.timeslot(sms.getTimeslot())
|
||||
@@ -312,8 +310,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
mic.update(hyteraSmsPacket.getSource());
|
||||
mic.update(hyteraSmsPacket.getDestination());
|
||||
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||
.eventDescription(DecodeEventType.SMS.name())
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
|
||||
.identifiers(mic)
|
||||
.timeslot(packet.getTimeslot())
|
||||
.details("SMS:" + hyteraSmsPacket.getSMS())
|
||||
@@ -325,8 +322,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||
.eventDescription(DecodeEventType.UNKNOWN_PACKET.name())
|
||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
||||
.identifiers(mic)
|
||||
.timeslot(packet.getTimeslot())
|
||||
.details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||
@@ -335,8 +331,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||
.eventDescription(DecodeEventType.DATA_PACKET.name())
|
||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||
.timeslot(packet.getTimeslot())
|
||||
.details(packet.toString())
|
||||
@@ -347,9 +342,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
|
||||
|
||||
if (geoPosition != null) {
|
||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp())
|
||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.eventDescription(DecodeEventType.GPS.toString())
|
||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||
.protocol(Protocol.LRRP)
|
||||
.location(geoPosition)
|
||||
@@ -505,14 +499,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD:
|
||||
if(csbk instanceof Acknowledge)
|
||||
{
|
||||
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.RESPONSE.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details(((Acknowledge)csbk).getReason().toString())
|
||||
.build();
|
||||
|
||||
broadcast(ackEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE,
|
||||
((Acknowledge) csbk).getReason().toString()));
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
||||
break;
|
||||
@@ -520,14 +508,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC:
|
||||
if(csbk instanceof Acknowledge)
|
||||
{
|
||||
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.RESPONSE.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details(((Acknowledge)csbk).getReason().toString())
|
||||
.build();
|
||||
|
||||
broadcast(ackEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE,
|
||||
((Acknowledge) csbk).getReason().toString()));
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
@@ -537,33 +519,16 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
switch(((Ahoy)csbk).getServiceKind())
|
||||
{
|
||||
case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE:
|
||||
DecodeEvent registerEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details(DecodeEventType.REGISTER.toString())
|
||||
.build();
|
||||
broadcast(registerEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, DecodeEventType.REGISTER.toString()));
|
||||
break;
|
||||
case CANCEL_CALL_SERVICE:
|
||||
DecodeEvent cancelEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details("CANCEL CALL")
|
||||
.build();
|
||||
broadcast(cancelEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, "CANCEL CALL"));
|
||||
break;
|
||||
case SUPPLEMENTARY_SERVICE:
|
||||
if(csbk instanceof StunReviveKill)
|
||||
{
|
||||
DecodeEvent stunEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details(((StunReviveKill)csbk).getCommand() + " RADIO")
|
||||
.build();
|
||||
broadcast(stunEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
|
||||
((StunReviveKill)csbk).getCommand() + " RADIO"));
|
||||
}
|
||||
break;
|
||||
case FULL_DUPLEX_MS_TO_MS_PACKET_CALL_SERVICE:
|
||||
@@ -577,15 +542,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
if(csbk instanceof ServiceRadioCheck)
|
||||
{
|
||||
ServiceRadioCheck src = (ServiceRadioCheck)csbk;
|
||||
|
||||
DecodeEvent checkEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.RADIO_CHECK.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details(src.getServiceDescription() + " SERVICE FOR " +
|
||||
(src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO"))
|
||||
.build();
|
||||
broadcast(checkEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.RADIO_CHECK,
|
||||
src.getServiceDescription() + " SERVICE FOR " +
|
||||
(src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -599,14 +558,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(aloha.hasRadioIdentifier())
|
||||
{
|
||||
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.RESPONSE.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details("Aloha Acknowledge")
|
||||
.build();
|
||||
|
||||
broadcast(ackEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge"));
|
||||
resetState();
|
||||
}
|
||||
}
|
||||
@@ -618,24 +570,13 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
switch(((Announcement)csbk).getAnnouncementType())
|
||||
{
|
||||
case MASS_REGISTRATION:
|
||||
DecodeEvent massEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.REGISTER.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details("MASS REGISTRATION")
|
||||
.build();
|
||||
broadcast(massEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.REGISTER, "MASS REGISTRATION"));
|
||||
break;
|
||||
case VOTE_NOW_ADVICE:
|
||||
if(csbk instanceof VoteNowAdvice)
|
||||
{
|
||||
DecodeEvent voteEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details("VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode())
|
||||
.build();
|
||||
broadcast(voteEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
|
||||
"VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -653,13 +594,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case STANDARD_PROTECT:
|
||||
if(csbk instanceof Protect)
|
||||
{
|
||||
DecodeEvent protectEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.COMMAND.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details("PROTECT: " + ((Protect)csbk).getProtectKind())
|
||||
.build();
|
||||
broadcast(protectEvent);
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
|
||||
"PROTECT: " + ((Protect)csbk).getProtectKind()));
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
|
||||
break;
|
||||
@@ -714,13 +650,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.DATA_CALL.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
else
|
||||
@@ -753,13 +683,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.CALL_GROUP.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
else
|
||||
@@ -791,13 +715,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.CALL_UNIT_TO_UNIT.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
else
|
||||
@@ -817,15 +735,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(cmAloha.hasRadioIdentifier())
|
||||
{
|
||||
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.eventDescription(DecodeEventType.RESPONSE.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details("Aloha Acknowledge")
|
||||
.build();
|
||||
|
||||
broadcast(ackEvent);
|
||||
|
||||
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge"));
|
||||
resetState();
|
||||
}
|
||||
}
|
||||
@@ -850,13 +760,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.DATA_CALL.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
|
||||
}
|
||||
else
|
||||
@@ -871,9 +775,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
||||
DecodeEvent event = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
||||
.details("Registration Request")
|
||||
.eventDescription(DecodeEventType.REGISTER.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.build();
|
||||
@@ -881,9 +784,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
||||
DecodeEvent regRespEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
||||
.details("Registration Response")
|
||||
.eventDescription(DecodeEventType.REGISTER.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.build();
|
||||
@@ -909,13 +811,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
detectedEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.eventDescription(DecodeEventType.CALL_GROUP.toString())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel, mergedIdentifiers);
|
||||
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent);
|
||||
}
|
||||
else
|
||||
@@ -930,9 +826,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
||||
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
|
||||
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
||||
.details("TALKGROUP AFFILIATION")
|
||||
.eventDescription(DecodeEventType.AFFILIATE.toString())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.build();
|
||||
@@ -945,6 +840,23 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel, IdentifierCollection mergedIdentifiers) {
|
||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.identifiers(mergedIdentifiers)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
|
||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(csbk.getTimeslot())
|
||||
.details(details)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the current identifier collection, removes any USER class identifiers and loads the identifiers
|
||||
* argument values into the collection.
|
||||
@@ -1191,8 +1103,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
ic.update(gpsInformation.getGPSLocation());
|
||||
|
||||
DecodeEvent gpsEvent = DMRDecodeEvent.builder(message.getTimestamp())
|
||||
.eventDescription(DecodeEventType.GPS.toString())
|
||||
DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp())
|
||||
.identifiers(ic)
|
||||
.timeslot(message.getTimeslot())
|
||||
.details("LOCATION:" + gpsInformation.getGPSLocation())
|
||||
@@ -1223,9 +1134,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(mCurrentCallEvent == null)
|
||||
{
|
||||
mCurrentCallEvent = DMRDecodeEvent.builder(timestamp)
|
||||
mCurrentCallEvent = DMRDecodeEvent.builder(type, timestamp)
|
||||
.channel(getCurrentChannel())
|
||||
.eventDescription(type.toString())
|
||||
.details(details)
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.build();
|
||||
@@ -1236,7 +1146,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
if(type != DecodeEventType.CALL)
|
||||
{
|
||||
mCurrentCallEvent.setEventDescription(type.toString());
|
||||
mCurrentCallEvent.setDetails(details);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -55,9 +55,6 @@ import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import io.github.dsheirer.source.tuner.channel.rotation.DisableChannelRotationMonitorRequest;
|
||||
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -66,6 +63,8 @@ import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
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
|
||||
@@ -165,7 +164,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
{
|
||||
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.setSystem(mParentChannel.getSystem());
|
||||
trafficChannel.setSite(mParentChannel.getSite());
|
||||
@@ -326,12 +325,12 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
int lsn = channel.getLogicalSlotNumber();
|
||||
|
||||
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
|
||||
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||
|
||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||
{
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
||||
.channel(channel)
|
||||
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString())
|
||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
@@ -350,10 +349,9 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
{
|
||||
event.end(timestamp);
|
||||
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
||||
.channel(channel)
|
||||
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString() + " - Continue")
|
||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
|
||||
@@ -390,15 +388,6 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
{
|
||||
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)
|
||||
{
|
||||
event.setDetails(DATA_CALL_IGNORED);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,12 +14,13 @@
|
||||
*
|
||||
* 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.event;
|
||||
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
|
||||
/**
|
||||
* DMR Channel Grant Event.
|
||||
@@ -33,11 +33,12 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param decodeEventType 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
|
||||
* @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 mDuration;
|
||||
protected String mEventDescription;
|
||||
protected DecodeEventType mDecodeEventType;
|
||||
protected IdentifierCollection mIdentifierCollection;
|
||||
protected DMRChannel mChannel;
|
||||
protected String mDetails;
|
||||
@@ -85,8 +86,9 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
||||
*
|
||||
* @param timeStart
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder(long timeStart)
|
||||
public DMRChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
mDecodeEventType = decodeEventType;
|
||||
mTimeStart = timeStart;
|
||||
}
|
||||
|
||||
@@ -121,7 +123,7 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
||||
|
||||
/**
|
||||
* Sets the channel descriptor for this event
|
||||
* @param channelDescriptor
|
||||
* @param channel
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel)
|
||||
{
|
||||
@@ -129,16 +131,6 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
||||
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.
|
||||
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
|
||||
@@ -165,12 +157,11 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
|
||||
*/
|
||||
public DMRChannelGrantEvent build()
|
||||
{
|
||||
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mTimeStart);
|
||||
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mDecodeEventType, mTimeStart);
|
||||
decodeEvent.setChannelDescriptor(mChannel);
|
||||
decodeEvent.setTimeslot(mChannel.getTimeslot());
|
||||
decodeEvent.setDetails(mDetails);
|
||||
decodeEvent.setDuration(mDuration);
|
||||
decodeEvent.setEventDescription(mEventDescription);
|
||||
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
||||
return decodeEvent;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,12 +14,13 @@
|
||||
*
|
||||
* 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.event;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
@@ -32,9 +32,9 @@ public class DMRDecodeEvent extends DecodeEvent
|
||||
* Constucts a decode event
|
||||
* @param start
|
||||
*/
|
||||
public DMRDecodeEvent(long start)
|
||||
public DMRDecodeEvent(DecodeEventType decodeEventType, long start)
|
||||
{
|
||||
super(start);
|
||||
super(decodeEventType, start);
|
||||
setProtocol(Protocol.DMR);
|
||||
}
|
||||
|
||||
@@ -43,9 +43,9 @@ public class DMRDecodeEvent extends DecodeEvent
|
||||
* @param timeStart for the event
|
||||
* @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);
|
||||
return decodeEventBuilder;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public enum Opcode
|
||||
STANDARD_CHANNEL_TIMING(Vendor.STANDARD, 7, "CHANNEL TIMING"),
|
||||
STANDARD_ALOHA(Vendor.STANDARD, 25, "ALOHA"),
|
||||
STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER(Vendor.STANDARD, 26, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
||||
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
||||
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT INBOUND HEADER"),
|
||||
STANDARD_AHOY(Vendor.STANDARD, 28, "AHOY"),
|
||||
STANDARD_ACTIVATION(Vendor.STANDARD, 30, "ACTIVATION"),
|
||||
STANDARD_RANDOM_ACCESS_SERVICE_REQUEST(Vendor.STANDARD, 31, "RANDOM ACCESS SERVICE REQUEST"),
|
||||
@@ -102,13 +102,18 @@ public enum Opcode
|
||||
HYTERA_68_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA 68 XPT PREAMBLE"),
|
||||
HYTERA_68_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA 68 CSBKO 62"),
|
||||
|
||||
|
||||
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
||||
|
||||
private Vendor mVendor;
|
||||
private int mValue;
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param vendor for the opcode
|
||||
* @param value for the opcode
|
||||
* @param label to display for the opcode
|
||||
*/
|
||||
Opcode(Vendor vendor, int value, String label)
|
||||
{
|
||||
mVendor = vendor;
|
||||
@@ -151,19 +156,86 @@ public enum Opcode
|
||||
*/
|
||||
public boolean isDataChannelGrantOpcode()
|
||||
{
|
||||
return DATA_CHANNEL_GRANT_OPCODES.contains(this);
|
||||
return DATA_CHANNEL_GRANTS.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Channel Grant opcodes
|
||||
* Data channel grant opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> DATA_CHANNEL_GRANT_OPCODES = EnumSet.of(
|
||||
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM,
|
||||
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM,
|
||||
STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT,
|
||||
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM,
|
||||
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM);
|
||||
public static final EnumSet<Opcode> DATA_CHANNEL_GRANTS = EnumSet.of(STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM,
|
||||
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM, STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT,
|
||||
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM, STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM);
|
||||
|
||||
/**
|
||||
* Data opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> DATA_OPCODES = EnumSet.of(STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER,
|
||||
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER, STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER,
|
||||
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER, STANDARD_PREAMBLE);
|
||||
|
||||
/**
|
||||
* Mobile request and response opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> MOBILE_REQUEST_RESPONSE = EnumSet.of(STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST,
|
||||
STANDARD_RANDOM_ACCESS_SERVICE_REQUEST);
|
||||
|
||||
/**
|
||||
* Network request and response and announcement opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> NETWORK_REQUEST_RESPONSE = EnumSet.of(STANDARD_FEATURE_NOT_SUPPORTED,
|
||||
STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_RESPONSE, STANDARD_AHOY, STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC,
|
||||
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_TSCC, STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD,
|
||||
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD, STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE, STANDARD_CLEAR,
|
||||
STANDARD_MOVE_TSCC, STANDARD_CHANNEL_TIMING,
|
||||
STANDARD_ALOHA, STANDARD_ACTIVATION, STANDARD_ANNOUNCEMENT, STANDARD_MAINTENANCE, STANDARD_PROTECT);
|
||||
|
||||
/**
|
||||
* Voice channel grant opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> VOICE_CHANNEL_GRANTS = EnumSet.of(STANDARD_PRIVATE_VOICE_CHANNEL_GRANT,
|
||||
STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT, STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT,
|
||||
STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT);
|
||||
|
||||
/**
|
||||
* Hytera opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> HYTERA = EnumSet.of(HYTERA_08_ACKNOWLEDGE, HYTERA_08_ANNOUNCEMENT,
|
||||
HYTERA_08_CSBKO_44, HYTERA_08_CSBKO_47, HYTERA_68_XPT_SITE_STATE, HYTERA_68_ALOHA,
|
||||
HYTERA_68_ACKNOWLEDGE, HYTERA_68_ANNOUNCEMENT, HYTERA_68_XPT_PREAMBLE, HYTERA_68_CSBKO_62);
|
||||
|
||||
/**
|
||||
* Motorola Capacity Max opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> MOTOROLA_CAPACITY_MAX = EnumSet.of(MOTOROLA_CAPMAX_ALOHA);
|
||||
|
||||
/**
|
||||
* Motorola Capacity Plus opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> MOTOROLA_CAPACITY_PLUS = EnumSet.of(MOTOROLA_CAPPLUS_CALL_ALERT,
|
||||
MOTOROLA_CAPPLUS_CALL_ALERT_ACK, MOTOROLA_CAPPLUS_DATA_WINDOW_ANNOUNCEMENT,
|
||||
MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT, MOTOROLA_CAPPLUS_NEIGHBOR_REPORT, MOTOROLA_CAPPLUS_CSBKO_60,
|
||||
MOTOROLA_CAPPLUS_PREAMBLE, MOTOROLA_CAPPLUS_SITE_STATUS);
|
||||
|
||||
/**
|
||||
* Motorola Connect Plus opcodes
|
||||
*/
|
||||
public static final EnumSet<Opcode> MOTOROLA_CONNECT_PLUS = EnumSet.of(MOTOROLA_CONPLUS_NEIGHBOR_REPORT,
|
||||
MOTOROLA_CONPLUS_VOICE_CHANNEL_USER, MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT, MOTOROLA_CONPLUS_CSBKO_10,
|
||||
MOTOROLA_CONPLUS_TERMINATE_CHANNEL_GRANT, MOTOROLA_CONPLUS_CSBKO_16, MOTOROLA_CONPLUS_REGISTRATION_REQUEST,
|
||||
MOTOROLA_CONPLUS_REGISTRATION_RESPONSE, MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION,
|
||||
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT, MOTOROLA_CONPLUS_DATA_WINDOW_GRANT);
|
||||
|
||||
/**
|
||||
* Indicates if the opcode is included in one of the enumset groupings above
|
||||
* @return
|
||||
*/
|
||||
public boolean isGrouped()
|
||||
{
|
||||
return DATA_CHANNEL_GRANTS.contains(this) || DATA_OPCODES.contains(this) ||
|
||||
MOBILE_REQUEST_RESPONSE.contains(this) || NETWORK_REQUEST_RESPONSE.contains(this) ||
|
||||
VOICE_CHANNEL_GRANTS.contains(this) || HYTERA.contains(this) || MOTOROLA_CAPACITY_MAX.contains(this) ||
|
||||
MOTOROLA_CAPACITY_PLUS.contains(this) || MOTOROLA_CONNECT_PLUS.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup map of vendors and opcode value to opcodes
|
||||
|
||||
@@ -67,14 +67,14 @@ public enum LCOpcode
|
||||
//CAP+ SLCO 15 = Rest Channel https://forums.radioreference.com/threads/understanding-capacity-plus-trunking.209318/page-7
|
||||
SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION(Vendor.MOTOROLA_CAPACITY_PLUS, false,15, "REST CHANNEL NOTIFICATION"),
|
||||
|
||||
SHORT_STANDARD_XPT_CHANNEL(Vendor.STANDARD, false, 8, "HYTERA XPT CHANNEL"),
|
||||
SHORT_STANDARD_XPT_CHANNEL(Vendor.STANDARD, false, 8, "STANDARD XPT CHANNEL"),
|
||||
SHORT_HYTERA_XPT_CHANNEL(Vendor.HYTERA_68, false, 8, "HYTERA XPT CHANNEL"),
|
||||
|
||||
SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL(Vendor.STANDARD, false, 9, "TRAFFIC CHANNEL INFO"),
|
||||
SHORT_CONNECT_PLUS_CONTROL_CHANNEL(Vendor.STANDARD, false, 10, "CONTROL CHANNEL INFO"),
|
||||
|
||||
|
||||
SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "SHORT UNKNOWN");
|
||||
SHORT_STANDARD_UNKNOWN(Vendor.STANDARD,false,-1, "UNKNOWN");
|
||||
|
||||
private static final EnumSet<LCOpcode> TALKER_ALIAS_OPCODES = EnumSet.of(FULL_STANDARD_TALKER_ALIAS_HEADER,
|
||||
FULL_STANDARD_TALKER_ALIAS_BLOCK_1, FULL_STANDARD_TALKER_ALIAS_BLOCK_2, FULL_STANDARD_TALKER_ALIAS_BLOCK_3);
|
||||
|
||||
@@ -63,7 +63,6 @@ public class PacketSequenceMessageFactory
|
||||
if(packetSequence.hasPacketSequenceHeader())
|
||||
{
|
||||
PacketSequenceHeader primaryHeader = packetSequence.getPacketSequenceHeader();
|
||||
boolean confirmed = primaryHeader.isConfirmedData();
|
||||
CorrectedBinaryMessage packet = getPacket(packetSequence, primaryHeader.isConfirmedData());
|
||||
|
||||
if(packet != null)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Filter for opcode based control (CSBK) messages.
|
||||
*/
|
||||
public class ControlMessageFilter extends Filter<IMessage, Opcode>
|
||||
{
|
||||
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param name of this control message filter
|
||||
* @param opcodes to use with this filter
|
||||
*/
|
||||
public ControlMessageFilter(String name, Collection<Opcode> opcodes)
|
||||
{
|
||||
super(name);
|
||||
|
||||
for(Opcode opcode: opcodes)
|
||||
{
|
||||
add(new FilterElement<>(opcode));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<IMessage, Opcode> getKeyExtractor()
|
||||
{
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor
|
||||
*/
|
||||
private class KeyExtractor implements Function<IMessage,Opcode>
|
||||
{
|
||||
@Override
|
||||
public Opcode apply(IMessage message)
|
||||
{
|
||||
if(message instanceof CSBKMessage csbk)
|
||||
{
|
||||
return csbk.getOpcode();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.filter;
|
||||
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Filter set for DMR control (CSBK) messages using Opcodes for filter elements.
|
||||
*/
|
||||
public class ControlMessageFilterSet extends FilterSet<IMessage>
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public ControlMessageFilterSet()
|
||||
{
|
||||
super("Control (CSBK) Messages");
|
||||
addFilter(new ControlMessageFilter("Data Channel Grants", Opcode.DATA_CHANNEL_GRANTS));
|
||||
addFilter(new ControlMessageFilter("Data-Related", Opcode.DATA_OPCODES));
|
||||
addFilter(new ControlMessageFilter("Mobile Request/Response", Opcode.MOBILE_REQUEST_RESPONSE));
|
||||
addFilter(new ControlMessageFilter("Network Request/Response", Opcode.NETWORK_REQUEST_RESPONSE));
|
||||
addFilter(new ControlMessageFilter("Voice Channel Grants", Opcode.VOICE_CHANNEL_GRANTS));
|
||||
addFilter(new ControlMessageFilter("Hytera", Opcode.HYTERA));
|
||||
addFilter(new ControlMessageFilter("Motorola Capacity Max", Opcode.MOTOROLA_CAPACITY_MAX));
|
||||
addFilter(new ControlMessageFilter("Motorola Capacity Plus", Opcode.MOTOROLA_CAPACITY_PLUS));
|
||||
addFilter(new ControlMessageFilter("Motorola Connect Plus", Opcode.MOTOROLA_CONNECT_PLUS));
|
||||
|
||||
//Add all remaining opcodes that are not grouped into one of the above enumsets.
|
||||
List<Opcode> otherOpcodes = new ArrayList<>();
|
||||
for(Opcode opcode: Opcode.values())
|
||||
{
|
||||
if(!opcode.isGrouped())
|
||||
{
|
||||
otherOpcodes.add(opcode);
|
||||
}
|
||||
}
|
||||
|
||||
addFilter(new ControlMessageFilter("Other/Unknown", otherOpcodes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof CSBKMessage && super.canProcess(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Filter for data messages, excluding CSBK messages.
|
||||
*/
|
||||
public class DataMessageFilter extends Filter<IMessage, DataType>
|
||||
{
|
||||
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param name of this filter
|
||||
*/
|
||||
public DataMessageFilter()
|
||||
{
|
||||
super("Data Messages");
|
||||
|
||||
for(DataType dataType: DataType.values())
|
||||
{
|
||||
add(new FilterElement<>(dataType));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<IMessage, DataType> getKeyExtractor()
|
||||
{
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof DataMessage && !(message instanceof CSBKMessage) && super.canProcess(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor
|
||||
*/
|
||||
private class KeyExtractor implements Function<IMessage,DataType>
|
||||
{
|
||||
@Override
|
||||
public DataType apply(IMessage message)
|
||||
{
|
||||
if(message instanceof DataMessage dataMessage)
|
||||
{
|
||||
return dataMessage.getSlotType().getDataType();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.filter;
|
||||
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.filter.SyncLossMessageFilter;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
|
||||
/**
|
||||
* Filter set for DMR messages
|
||||
*/
|
||||
public class DmrMessageFilterSet extends FilterSet<IMessage>
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DmrMessageFilterSet()
|
||||
{
|
||||
super("DMR Messages");
|
||||
addFilter(new ControlMessageFilterSet());
|
||||
addFilter(new VoiceMessageFilter());
|
||||
addFilter(new DataMessageFilter());
|
||||
addFilter(new DmrPacketSequenceFilter());
|
||||
addFilter(new LinkControlMessageFilterSet());
|
||||
addFilter(new SyncLossMessageFilter());
|
||||
addFilter(new DmrOtherMessageFilter());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Filter for unknown messages, meaning messages not handled by any other filter.
|
||||
*/
|
||||
public class DmrOtherMessageFilter extends Filter<IMessage,String>
|
||||
{
|
||||
private static final String OTHER_KEY = "Other/Unknown Message";
|
||||
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DmrOtherMessageFilter()
|
||||
{
|
||||
super("Other DMR messages");
|
||||
add(new FilterElement<>(OTHER_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<IMessage, String> getKeyExtractor()
|
||||
{
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof DMRMessage && super.canProcess(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor
|
||||
*/
|
||||
private class KeyExtractor implements Function<IMessage,String>
|
||||
{
|
||||
@Override
|
||||
public String apply(IMessage message)
|
||||
{
|
||||
if(message instanceof DMRMessage)
|
||||
{
|
||||
return OTHER_KEY;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
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.ip.hytera.sds.HyteraUnknownPacket;
|
||||
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 java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Filter for DMR packet sequence messages
|
||||
*/
|
||||
public class DmrPacketSequenceFilter extends Filter<IMessage,String>
|
||||
{
|
||||
private static final String KEY_ARS = "Automatic Registration Service (ARS)";
|
||||
private static final String KEY_HYTERA_SMS = "Hytera Short Message Service (SMS)";
|
||||
private static final String KEY_HYTERA_UNKNOWN = "Hytera Unknown";
|
||||
private static final String KEY_LRRP = "Location Request/Response Protocol (LRRP)";
|
||||
private static final String KEY_UDT_SMS = "Unified Data Transport - Short Message Service";
|
||||
private static final String KEY_XCMP = "Extensible Command Message Protocol (XCMP)";
|
||||
private static final String KEY_UNKNOWN = "Other/Unknown";
|
||||
|
||||
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DmrPacketSequenceFilter()
|
||||
{
|
||||
super("Packet Sequence Messages");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof DMRPacketMessage && super.canProcess(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<IMessage, String> getKeyExtractor()
|
||||
{
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
private class KeyExtractor implements Function<IMessage,String>
|
||||
{
|
||||
@Override
|
||||
public String apply(IMessage message)
|
||||
{
|
||||
if(message instanceof UDTShortMessageService)
|
||||
{
|
||||
return KEY_UDT_SMS;
|
||||
}
|
||||
else if(message instanceof DMRPacketMessage packetMessage)
|
||||
{
|
||||
if(packetMessage.getPacket() instanceof ARSPacket)
|
||||
{
|
||||
return KEY_ARS;
|
||||
}
|
||||
else if(packetMessage.getPacket() instanceof LRRPPacket)
|
||||
{
|
||||
return KEY_LRRP;
|
||||
}
|
||||
else if(packetMessage.getPacket() instanceof XCMPPacket)
|
||||
{
|
||||
return KEY_XCMP;
|
||||
}
|
||||
else if(packetMessage.getPacket() instanceof HyteraSmsPacket)
|
||||
{
|
||||
return KEY_HYTERA_SMS;
|
||||
}
|
||||
else if(packetMessage.getPacket() instanceof HyteraUnknownPacket)
|
||||
{
|
||||
return KEY_HYTERA_UNKNOWN;
|
||||
}
|
||||
|
||||
//TODO: finish this
|
||||
}
|
||||
|
||||
return KEY_UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.dmr.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Message filter for link control opcodes.
|
||||
*
|
||||
* Note: this does not include link control messages carried by data messages with embedded link control.
|
||||
*/
|
||||
public class LinkControlMessageFilter extends Filter<IMessage, LCOpcode>
|
||||
{
|
||||
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param name of this filter
|
||||
*/
|
||||
public LinkControlMessageFilter(String name, Collection<LCOpcode> opcodes)
|
||||
{
|
||||
super(name);
|
||||
|
||||
for(LCOpcode opcode: opcodes)
|
||||
{
|
||||
add(new FilterElement<>(opcode));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<IMessage, LCOpcode> getKeyExtractor()
|
||||
{
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor
|
||||
*/
|
||||
private class KeyExtractor implements Function<IMessage,LCOpcode>
|
||||
{
|
||||
@Override
|
||||
public LCOpcode apply(IMessage message)
|
||||
{
|
||||
if(message instanceof LCMessage lcMessage)
|
||||
{
|
||||
return lcMessage.getOpcode();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.filter;
|
||||
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCOpcode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Filter set for short & voice-fragment link control messages
|
||||
*
|
||||
* Note: this filter set does NOT work for link control messages in Data Message with Link Control.
|
||||
*/
|
||||
public class LinkControlMessageFilterSet extends FilterSet<IMessage>
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public LinkControlMessageFilterSet()
|
||||
{
|
||||
super("Short/Voice-Fragment Link Control Messages");
|
||||
|
||||
List<LCOpcode> fullLinkControl = new ArrayList<>();
|
||||
List<LCOpcode> shortLinkControl = new ArrayList<>();
|
||||
for(LCOpcode opcode: LCOpcode.values())
|
||||
{
|
||||
if(opcode.isFull())
|
||||
{
|
||||
fullLinkControl.add(opcode);
|
||||
}
|
||||
else
|
||||
{
|
||||
shortLinkControl.add(opcode);
|
||||
}
|
||||
}
|
||||
|
||||
addFilter(new LinkControlMessageFilter("Full Link Control", fullLinkControl));
|
||||
addFilter(new LinkControlMessageFilter("Short Link Control", shortLinkControl));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof LCMessage && super.canProcess(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Filter for voice messages
|
||||
*/
|
||||
public class VoiceMessageFilter extends Filter<IMessage, DMRSyncPattern>
|
||||
{
|
||||
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public VoiceMessageFilter()
|
||||
{
|
||||
super("Voice Messages");
|
||||
add(new FilterElement<>(DMRSyncPattern.BASE_STATION_VOICE));
|
||||
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_B));
|
||||
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_C));
|
||||
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_D));
|
||||
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_E));
|
||||
add(new FilterElement<>(DMRSyncPattern.BS_VOICE_FRAME_F));
|
||||
add(new FilterElement<>(DMRSyncPattern.MOBILE_STATION_VOICE));
|
||||
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_B));
|
||||
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_C));
|
||||
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_D));
|
||||
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_E));
|
||||
add(new FilterElement<>(DMRSyncPattern.MS_VOICE_FRAME_F));
|
||||
add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_1));
|
||||
add(new FilterElement<>(DMRSyncPattern.DIRECT_MODE_VOICE_TIMESLOT_2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof VoiceMessage && super.canProcess(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<IMessage,DMRSyncPattern> getKeyExtractor()
|
||||
{
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor
|
||||
*/
|
||||
private class KeyExtractor implements Function<IMessage,DMRSyncPattern>
|
||||
{
|
||||
@Override
|
||||
public DMRSyncPattern apply(IMessage message)
|
||||
{
|
||||
if(message instanceof VoiceMessage voice)
|
||||
{
|
||||
return voice.getSyncPattern();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -27,18 +27,18 @@ public enum DataType
|
||||
VOICE_HEADER(1, 288, "VOICE HEADER"),
|
||||
TLC(2, 288, "TERMINATOR"),
|
||||
CSBK(3, 288, "CSBK"),
|
||||
MBC_HEADER(4, 288, "MBC HEADER"),
|
||||
MBC_BLOCK(5, 276, "MBC"),
|
||||
MBC_HEADER(4, 288, "MULTI-BLOCK CONTROL HEADER"),
|
||||
MBC_BLOCK(5, 276, "MULTI-BLOCK CONTROL BLOCK"),
|
||||
DATA_HEADER(6, 288, "DATA HEADER"),
|
||||
RATE_1_OF_2_DATA(7, 276, "RATE 1/2 PACKET"),
|
||||
RATE_3_OF_4_DATA(8, 276, "RATE 3/4 PACKET"),
|
||||
SLOT_IDLE(9, 276, "IDLE"),
|
||||
RATE_1_DATA(10, 276, "RATE 1/1 PACKET"),
|
||||
USB_DATA(11, 276, "UNIFIED SINGLE BLOCK DATA"),
|
||||
MBC_ENC_HEADER(12, 276, "MBC ENCRYPTED HEADER"),
|
||||
MBC_ENC_HEADER(12, 276, "MULTI-BLOCK CONTROL ENCRYPTED HEADER"),
|
||||
DATA_ENC_HEADER(13, 276, "DATA ENCRYPTED HEADER"),
|
||||
CHANNEL_CONTROL_ENC_HEADER(14, 276, "CONTROL CHANNEL ENCRYPTED HEADER"),
|
||||
RESERVED_15(15, -1, "RESERVED"),
|
||||
RESERVED_15(15, -1, "RESERVED 15"),
|
||||
UNKNOWN(-1, -1, "UNKNOWN");
|
||||
|
||||
private int mValue;
|
||||
|
||||
@@ -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.module.decode.event;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
/**
|
||||
* AbstractTableModel implementation supporting clearable method options.
|
||||
*/
|
||||
public abstract class ClearableHistoryModel<T> extends AbstractTableModel
|
||||
{
|
||||
public static final int DEFAULT_HISTORY_SIZE = 200;
|
||||
private LinkedList<T> mItems = new LinkedList<>();
|
||||
private int mHistorySize = DEFAULT_HISTORY_SIZE;
|
||||
|
||||
/**
|
||||
* Access an item/row by the model index value.
|
||||
* @param index to retrieve
|
||||
* @return item or null
|
||||
*/
|
||||
public T getItem(int index)
|
||||
{
|
||||
if(index < mItems.size())
|
||||
{
|
||||
return mItems.get(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the item to the top of the item list and removes any tail items while the item list size exceeds the
|
||||
* maximum history size for this model.
|
||||
* @param item to add
|
||||
*/
|
||||
public void add(T item)
|
||||
{
|
||||
mItems.addFirst(item);
|
||||
ClearableHistoryModel.this.fireTableRowsInserted(0, 0);
|
||||
while(mItems.size() > mHistorySize)
|
||||
{
|
||||
mItems.removeLast();
|
||||
super.fireTableRowsDeleted(mItems.size() - 1, mItems.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all messages from history
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
EventQueue.invokeLater(() -> {
|
||||
mItems.clear();
|
||||
fireTableDataChanged();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current messages and loads the messages argument
|
||||
*/
|
||||
public void clearAndSet(List<T> items)
|
||||
{
|
||||
EventQueue.invokeLater(() -> {
|
||||
mItems.clear();
|
||||
fireTableDataChanged();
|
||||
for(T item: items)
|
||||
{
|
||||
add(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Current history size
|
||||
* @return history size
|
||||
*/
|
||||
public int getHistorySize()
|
||||
{
|
||||
return mHistorySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the history size to the default value.
|
||||
*/
|
||||
public void resetHistorySize()
|
||||
{
|
||||
setHistorySize(DEFAULT_HISTORY_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the history size
|
||||
* @param historySize
|
||||
*/
|
||||
public void setHistorySize(int historySize)
|
||||
{
|
||||
mHistorySize = historySize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mItems.size();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -32,27 +32,33 @@ public class DecodeEvent implements IDecodeEvent
|
||||
{
|
||||
private long mTimeStart;
|
||||
private long mTimeEnd;
|
||||
private String mEventDescription;
|
||||
private DecodeEventType mDecodeEventType;
|
||||
private IdentifierCollection mIdentifierCollection;
|
||||
private IChannelDescriptor mChannelDescriptor;
|
||||
private String mDetails;
|
||||
private Protocol mProtocol;
|
||||
private Integer mTimeslot;
|
||||
private int mTimeslot = -1;
|
||||
|
||||
public DecodeEvent(long start)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param decodeEventType is mandatory to support event filtering by event type.
|
||||
* @param start time of the event.
|
||||
*/
|
||||
public DecodeEvent(DecodeEventType decodeEventType, long start)
|
||||
{
|
||||
mDecodeEventType = decodeEventType;
|
||||
mTimeStart = start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decode event builder with the specified start timestamp.
|
||||
* @param decodeEventType for the event
|
||||
* @param timeStart for the event
|
||||
* @return builder
|
||||
*/
|
||||
public static DecodeEventBuilder builder(long timeStart)
|
||||
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
return new DecodeEventBuilder(timeStart);
|
||||
return new DecodeEventBuilder(decodeEventType, timeStart);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,23 +121,6 @@ public class DecodeEvent implements IDecodeEvent
|
||||
mTimeEnd = mTimeStart + duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event description
|
||||
*/
|
||||
@Override
|
||||
public String getEventDescription()
|
||||
{
|
||||
return mEventDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the event description text
|
||||
*/
|
||||
public void setEventDescription(String description)
|
||||
{
|
||||
mEventDescription = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DecodeEventType}
|
||||
*/
|
||||
@@ -140,13 +129,6 @@ public class DecodeEvent implements IDecodeEvent
|
||||
return mDecodeEventType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link DecodeEventType}
|
||||
*/
|
||||
public void setEventType(DecodeEventType eventType) {
|
||||
this.mDecodeEventType = eventType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier collection for this event.
|
||||
*/
|
||||
@@ -222,24 +204,24 @@ public class DecodeEvent implements IDecodeEvent
|
||||
* @return timeslot or default value of 0
|
||||
*/
|
||||
@Override
|
||||
public Integer getTimeslot()
|
||||
public int getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this event specifies a timeslot
|
||||
* Indicates if this event specifies a timeslot that is not negative
|
||||
*/
|
||||
public boolean hasTimeslot()
|
||||
{
|
||||
return mTimeslot != null;
|
||||
return mTimeslot >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeslot for this event
|
||||
* @param timeslot of the event
|
||||
*/
|
||||
public void setTimeslot(Integer timeslot)
|
||||
public void setTimeslot(int timeslot)
|
||||
{
|
||||
mTimeslot = timeslot;
|
||||
}
|
||||
@@ -249,7 +231,7 @@ public class DecodeEvent implements IDecodeEvent
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(mProtocol);
|
||||
sb.append(" DECODE EVENT: ").append(getEventDescription());
|
||||
sb.append(" DECODE EVENT: ").append(getEventType());
|
||||
sb.append(" DETAILS:").append(getDetails());
|
||||
if(mIdentifierCollection != null)
|
||||
{
|
||||
@@ -267,19 +249,19 @@ public class DecodeEvent implements IDecodeEvent
|
||||
{
|
||||
protected long mTimeStart;
|
||||
protected long mDuration;
|
||||
protected String mEventDescription;
|
||||
protected DecodeEventType mDecodeEventType;
|
||||
protected IdentifierCollection mIdentifierCollection;
|
||||
protected IChannelDescriptor mChannelDescriptor;
|
||||
protected String mDetails;
|
||||
protected Protocol mProtocol = Protocol.UNKNOWN;
|
||||
protected Integer mTimeslot;
|
||||
protected int mTimeslot = -1;
|
||||
|
||||
/**
|
||||
* Constructs a builder instance with the specified start time in milliseconds
|
||||
*/
|
||||
public DecodeEventBuilder(long timeStart)
|
||||
public DecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
mDecodeEventType = decodeEventType;
|
||||
mTimeStart = timeStart;
|
||||
}
|
||||
|
||||
@@ -313,21 +295,6 @@ public class DecodeEvent implements IDecodeEvent
|
||||
return this;
|
||||
}
|
||||
|
||||
public DecodeEventBuilder eventType(DecodeEventType eventType) {
|
||||
mDecodeEventType = eventType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the event description text
|
||||
* @param description of the event
|
||||
*/
|
||||
public DecodeEventBuilder eventDescription(String description)
|
||||
{
|
||||
mEventDescription = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identifier collection.
|
||||
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
|
||||
@@ -359,7 +326,12 @@ public class DecodeEvent implements IDecodeEvent
|
||||
return this;
|
||||
}
|
||||
|
||||
public DecodeEventBuilder timeslot(Integer timeslot)
|
||||
/**
|
||||
* Sets the timeslot for this event
|
||||
* @param timeslot
|
||||
* @return
|
||||
*/
|
||||
public DecodeEventBuilder timeslot(int timeslot)
|
||||
{
|
||||
mTimeslot = timeslot;
|
||||
return this;
|
||||
@@ -370,11 +342,10 @@ public class DecodeEvent implements IDecodeEvent
|
||||
*/
|
||||
public DecodeEvent build()
|
||||
{
|
||||
DecodeEvent decodeEvent = new DecodeEvent(mTimeStart);
|
||||
DecodeEvent decodeEvent = new DecodeEvent(mDecodeEventType, mTimeStart);
|
||||
decodeEvent.setChannelDescriptor(mChannelDescriptor);
|
||||
decodeEvent.setDetails(mDetails);
|
||||
decodeEvent.setDuration(mDuration);
|
||||
decodeEvent.setEventDescription(mEventDescription);
|
||||
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
||||
decodeEvent.setProtocol(mProtocol);
|
||||
decodeEvent.setTimeslot(mTimeslot);
|
||||
|
||||
@@ -21,25 +21,20 @@ package io.github.dsheirer.module.decode.event;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.module.decode.event.filter.DecodeEventFilterSet;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.awt.EventQueue;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
public class DecodeEventModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
|
||||
/**
|
||||
* Decode event table model
|
||||
*/
|
||||
public class DecodeEventModel extends ClearableHistoryModel<IDecodeEvent> implements Listener<IDecodeEvent>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DecodeEventModel.class);
|
||||
|
||||
public static final int COLUMN_TIME = 0;
|
||||
public static final int COLUMN_DURATION = 1;
|
||||
public static final int COLUMN_EVENT = 2;
|
||||
@@ -50,12 +45,6 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
|
||||
public static final int COLUMN_CHANNEL = 7;
|
||||
public static final int COLUMN_FREQUENCY = 8;
|
||||
public static final int COLUMN_DETAILS = 9;
|
||||
|
||||
protected int mMaxMessages = 200;
|
||||
|
||||
protected List<IDecodeEvent> mEvents = new ArrayList<>();
|
||||
protected FilterSet<IDecodeEvent> mEventFilterSet = new DecodeEventFilterSet();
|
||||
|
||||
protected String[] mHeaders = new String[]{"Time", "Duration", "Event", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Details"};
|
||||
|
||||
public DecodeEventModel()
|
||||
@@ -70,72 +59,10 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
|
||||
@Subscribe
|
||||
public void preferenceUpdated(PreferenceType preferenceType)
|
||||
{
|
||||
if(preferenceType == PreferenceType.DECODE_EVENT)
|
||||
if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.TALKGROUP_FORMAT)
|
||||
{
|
||||
for(int row = 0; row < mEvents.size(); row++)
|
||||
{
|
||||
fireTableCellUpdated(row, COLUMN_TIME);
|
||||
}
|
||||
fireTableDataChanged();
|
||||
}
|
||||
else if(preferenceType == PreferenceType.TALKGROUP_FORMAT)
|
||||
{
|
||||
for(int row = 0; row < mEvents.size(); row++)
|
||||
{
|
||||
fireTableCellUpdated(row, COLUMN_FROM_ID);
|
||||
fireTableCellUpdated(row, COLUMN_TO_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the complete list of events managed by this model.
|
||||
*/
|
||||
public List<IDecodeEvent> getEvents()
|
||||
{
|
||||
return new ArrayList<>(mEvents);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
Iterator<IDecodeEvent> it = mEvents.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
mEvents.clear();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all events from this model and loads the events argument
|
||||
*/
|
||||
public void clearAndSet(List<IDecodeEvent> events)
|
||||
{
|
||||
mEvents.clear();
|
||||
mEvents.addAll(events);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
dispose();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public int getMaxMessageCount()
|
||||
{
|
||||
return mMaxMessages;
|
||||
}
|
||||
|
||||
public void setMaxMessageCount(int count)
|
||||
{
|
||||
mMaxMessages = count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,49 +73,7 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
|
||||
*/
|
||||
public void receive(final IDecodeEvent event)
|
||||
{
|
||||
//Disabled until #1368 decode event is fully implemented
|
||||
// if (!mEventFilterSet.passes(event))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if(!mEvents.contains(event))
|
||||
{
|
||||
mEvents.add(0, event);
|
||||
fireTableRowsInserted(0, 0);
|
||||
prune();
|
||||
}
|
||||
else
|
||||
{
|
||||
int row = mEvents.indexOf(event);
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
}
|
||||
|
||||
private void prune()
|
||||
{
|
||||
while(mEvents.size() > mMaxMessages)
|
||||
{
|
||||
int index = mEvents.size() - 1;
|
||||
mEvents.remove(index);
|
||||
fireTableRowsDeleted(index, index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterSet<IDecodeEvent> getFilterSet() {
|
||||
return mEventFilterSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterSet(FilterSet<IDecodeEvent> filterSet) {
|
||||
this.mEventFilterSet = filterSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mEvents.size();
|
||||
EventQueue.invokeLater(() -> add(event));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -205,58 +90,55 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
synchronized(mEvents)
|
||||
IDecodeEvent event = getItem(rowIndex);
|
||||
|
||||
if(event != null)
|
||||
{
|
||||
IDecodeEvent event = mEvents.get(rowIndex);
|
||||
|
||||
if(event != null)
|
||||
switch(columnIndex)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_TIME:
|
||||
return event.getTimeStart();
|
||||
case COLUMN_DURATION:
|
||||
return event.getDuration();
|
||||
case COLUMN_EVENT:
|
||||
return event.getEventDescription();
|
||||
case COLUMN_FROM_ID:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_FROM_ALIAS:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_TO_ID:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_TO_ALIAS:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_CHANNEL:
|
||||
IChannelDescriptor channelDescriptor = event.getChannelDescriptor();
|
||||
case COLUMN_TIME:
|
||||
return event.getTimeStart();
|
||||
case COLUMN_DURATION:
|
||||
return event.getDuration();
|
||||
case COLUMN_EVENT:
|
||||
return event.getEventType().getLabel();
|
||||
case COLUMN_FROM_ID:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_FROM_ALIAS:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_TO_ID:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_TO_ALIAS:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_CHANNEL:
|
||||
IChannelDescriptor channelDescriptor = event.getChannelDescriptor();
|
||||
|
||||
if(channelDescriptor != null)
|
||||
if(channelDescriptor != null)
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
{
|
||||
return channelDescriptor.toString() + " TS:" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
return channelDescriptor.toString();
|
||||
}
|
||||
return channelDescriptor + " TS:" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
{
|
||||
return "TS:" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return channelDescriptor.toString();
|
||||
}
|
||||
case COLUMN_FREQUENCY:
|
||||
return event.getChannelDescriptor();
|
||||
case COLUMN_DETAILS:
|
||||
return event.getDetails();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
{
|
||||
return "TS:" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case COLUMN_FREQUENCY:
|
||||
return event.getChannelDescriptor();
|
||||
case COLUMN_DETAILS:
|
||||
return event.getDetails();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.filter.AllPassFilter;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
@@ -33,10 +32,7 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventClearButton;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventClearHandler;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterButton;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
||||
import io.github.dsheirer.module.decode.event.filter.DecodeEventFilterSet;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
@@ -58,7 +54,10 @@ import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.RowFilter;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
|
||||
public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain>
|
||||
{
|
||||
@@ -75,7 +74,10 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
private AliasModel mAliasModel;
|
||||
private UserPreferences mUserPreferences;
|
||||
private TimestampCellRenderer mTimestampCellRenderer;
|
||||
private EventManagementPanel mEventManagementPanel;
|
||||
private FilterSet<IDecodeEvent> mFilterSet = new DecodeEventFilterSet();
|
||||
private TableRowSorter<TableModel> mTableRowSorter;
|
||||
private HistoryManagementPanel<IDecodeEvent> mHistoryManagementPanel;
|
||||
|
||||
|
||||
/**
|
||||
* View for call event table
|
||||
@@ -91,17 +93,20 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
mUserPreferences = userPreferences;
|
||||
mTimestampCellRenderer = new TimestampCellRenderer();
|
||||
mTable = new JTable(mEventModel);
|
||||
mTable.setAutoCreateRowSorter(true);
|
||||
mTableRowSorter = new TableRowSorter<>(mEventModel);
|
||||
mTableRowSorter.setRowFilter(new EventRowFilter());
|
||||
mTable.setRowSorter(mTableRowSorter);
|
||||
mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||
mEventManagementPanel = new EventManagementPanel();
|
||||
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||
updateCellRenderers();
|
||||
|
||||
//Disabled until #1368 decode event is fully implemented
|
||||
// add(mEventManagementPanel, "span,growx");
|
||||
|
||||
mHistoryManagementPanel = new HistoryManagementPanel<>(mEventModel, "Event Filter Editor");
|
||||
mHistoryManagementPanel.updateFilterSet(mFilterSet);
|
||||
add(mHistoryManagementPanel, "span,growx");
|
||||
mEmptyScroller = new JScrollPane(mTable);
|
||||
add(mEmptyScroller);
|
||||
|
||||
//Register filter change listener to refresh the table any time the event filters are changed.
|
||||
mFilterSet.register(() -> mEventModel.fireTableDataChanged());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -124,22 +129,14 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
|
||||
private void updateCellRenderers()
|
||||
{
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TIME)
|
||||
.setCellRenderer(mTimestampCellRenderer);
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_DURATION)
|
||||
.setCellRenderer(new DurationCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ID)
|
||||
.setCellRenderer(new IdentifierCellRenderer(Role.FROM));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ALIAS)
|
||||
.setCellRenderer(new AliasedIdentifierCellRenderer(Role.FROM));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ID)
|
||||
.setCellRenderer(new IdentifierCellRenderer(Role.TO));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ALIAS)
|
||||
.setCellRenderer(new AliasedIdentifierCellRenderer(Role.TO));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_CHANNEL)
|
||||
.setCellRenderer(new ChannelDescriptorCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FREQUENCY)
|
||||
.setCellRenderer(new FrequencyCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TIME).setCellRenderer(mTimestampCellRenderer);
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_DURATION).setCellRenderer(new DurationCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ID).setCellRenderer(new IdentifierCellRenderer(Role.FROM));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ALIAS).setCellRenderer(new AliasedIdentifierCellRenderer(Role.FROM));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ID).setCellRenderer(new IdentifierCellRenderer(Role.TO));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ALIAS).setCellRenderer(new AliasedIdentifierCellRenderer(Role.TO));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_CHANNEL).setCellRenderer(new ChannelDescriptorCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FREQUENCY).setCellRenderer(new FrequencyCellRenderer());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -156,76 +153,17 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
mCurrentEventHistory = processingChain.getDecodeEventHistory();
|
||||
mEventModel.clearAndSet(mCurrentEventHistory.getItems());
|
||||
processingChain.getDecodeEventHistory().addListener(mEventModel);
|
||||
mEventManagementPanel.enableButtons();
|
||||
mHistoryManagementPanel.setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentEventHistory = null;
|
||||
mEventModel.clearAndSet(Collections.emptyList());
|
||||
mEventManagementPanel.disableButtons();
|
||||
mHistoryManagementPanel.setEnabled(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public class EventManagementPanel extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private EventFilterButton<IDecodeEvent> mFilterButton;
|
||||
private EventClearButton mEventClearButton;
|
||||
|
||||
public EventManagementPanel()
|
||||
{
|
||||
setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", ""));
|
||||
|
||||
initializeFilterButton();
|
||||
initializeClearButton();
|
||||
disableButtons();
|
||||
|
||||
add(mFilterButton);
|
||||
add(mEventClearButton);
|
||||
}
|
||||
|
||||
public void enableButtons()
|
||||
{
|
||||
mFilterButton.setEnabled(true);
|
||||
mEventClearButton.setEnabled(true);
|
||||
}
|
||||
|
||||
public void disableButtons()
|
||||
{
|
||||
mFilterButton.setEnabled(false);
|
||||
mEventClearButton.setEnabled(false);
|
||||
}
|
||||
|
||||
private void initializeFilterButton()
|
||||
{
|
||||
EventFilterProvider<IDecodeEvent> filterProvider = (EventFilterProvider<IDecodeEvent>) mTable.getModel();
|
||||
FilterSet<IDecodeEvent> filterSet = new FilterSet<>(new AllPassFilter<>());
|
||||
if (filterProvider != null) {
|
||||
filterSet = filterProvider.getFilterSet();
|
||||
}
|
||||
mFilterButton = new EventFilterButton<>("Message Filter Editor", filterSet);
|
||||
}
|
||||
|
||||
private void initializeClearButton() {
|
||||
mEventClearButton = new EventClearButton(
|
||||
((DecodeEventModel) mTable.getModel()).getMaxMessageCount()
|
||||
);
|
||||
mEventClearButton.setEventClearHandler(new EventClearHandler() {
|
||||
@Override
|
||||
public void onHistoryLimitChanged(int newHistoryLimit) {
|
||||
((DecodeEventModel) mTable.getModel()).setMaxMessageCount(newHistoryLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearHistoryClicked() {
|
||||
((DecodeEventModel) mTable.getModel()).clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom cell renderer for displaying identifiers from an identifier collection
|
||||
*/
|
||||
@@ -479,4 +417,26 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Row filter for decode events
|
||||
*/
|
||||
public class EventRowFilter extends RowFilter<TableModel, Integer>
|
||||
{
|
||||
@Override
|
||||
public boolean include(Entry<? extends TableModel, ? extends Integer> entry)
|
||||
{
|
||||
if(entry.getModel() instanceof DecodeEventModel model)
|
||||
{
|
||||
IDecodeEvent event = model.getItem(entry.getIdentifier());
|
||||
|
||||
if(event != null)
|
||||
{
|
||||
return mFilterSet.canProcess(event) && mFilterSet.passes(event);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,17 @@
|
||||
|
||||
package io.github.dsheirer.module.decode.event;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Enumeration of event types for decoded events.
|
||||
*/
|
||||
public enum DecodeEventType
|
||||
{
|
||||
AFFILIATE("Affiliate"),
|
||||
ANNOUNCEMENT("Announcement"),
|
||||
ACKNOWLEDGE("Acknowledge"),
|
||||
AUTOMATIC_REGISTRATION_SERVICE("Motorola ARS"),
|
||||
CALL("Call"),
|
||||
CALL_ENCRYPTED("Encrypted Call"),
|
||||
@@ -32,6 +39,7 @@ public enum DecodeEventType
|
||||
CALL_PATCH_GROUP_ENCRYPTED("Encrypted Patch Call"),
|
||||
CALL_ALERT("Call Alert"),
|
||||
CALL_DETECT("Call Detect"),
|
||||
CALL_IN_PROGRESS("Call In Progress"),
|
||||
CALL_DO_NOT_MONITOR("Call-Do Not Monitor"),
|
||||
CALL_END("Call End"),
|
||||
CALL_INTERCONNECT("Telephone Call"),
|
||||
@@ -41,6 +49,7 @@ public enum DecodeEventType
|
||||
CALL_UNIT_TO_UNIT_ENCRYPTED("Encrypted Unit To Unit Call"),
|
||||
CALL_NO_TUNER("Call - No Tuner"),
|
||||
CALL_TIMEOUT("Call Timeout"),
|
||||
CELLOCATOR("Cellocator"),
|
||||
COMMAND("Command"),
|
||||
DATA_CALL("Data Call"),
|
||||
DATA_CALL_ENCRYPTED("Encrypted Data Call"),
|
||||
@@ -53,6 +62,7 @@ public enum DecodeEventType
|
||||
ID_ANI("ANI"),
|
||||
ID_UNIQUE("Unique ID"),
|
||||
IP_PACKET("IP Packet"),
|
||||
LRRP("Motorola LRRP"),
|
||||
NOTIFICATION("Notification"),
|
||||
PAGE("Page"),
|
||||
QUERY("Query"),
|
||||
@@ -72,16 +82,82 @@ public enum DecodeEventType
|
||||
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Encrypted voice call event types for filtering
|
||||
*/
|
||||
public static final EnumSet<DecodeEventType> VOICE_CALLS_ENCRYPTED = EnumSet.of(DecodeEventType.CALL_ENCRYPTED,
|
||||
DecodeEventType.CALL_GROUP_ENCRYPTED, DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED,
|
||||
DecodeEventType.CALL_INTERCONNECT_ENCRYPTED, DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED);
|
||||
|
||||
/**
|
||||
* Voice call event types for filtering
|
||||
*/
|
||||
public static final EnumSet<DecodeEventType> VOICE_CALLS = EnumSet.of(DecodeEventType.CALL_GROUP,
|
||||
DecodeEventType.CALL_PATCH_GROUP, DecodeEventType.CALL_ALERT, DecodeEventType.CALL_DETECT,
|
||||
DecodeEventType.CALL_DO_NOT_MONITOR, DecodeEventType.CALL_END, DecodeEventType.CALL_INTERCONNECT,
|
||||
DecodeEventType.CALL_UNIQUE_ID, DecodeEventType.CALL_UNIT_TO_UNIT, DecodeEventType.CALL_NO_TUNER,
|
||||
DecodeEventType.CALL_TIMEOUT);
|
||||
|
||||
/**
|
||||
* Command event types for filtering
|
||||
*/
|
||||
public static final EnumSet<DecodeEventType> COMMANDS = EnumSet.of(DecodeEventType.ANNOUNCEMENT,
|
||||
DecodeEventType.STATION_ID, DecodeEventType.ACKNOWLEDGE, DecodeEventType.PAGE, DecodeEventType.QUERY,
|
||||
DecodeEventType.RADIO_CHECK, DecodeEventType.STATUS, DecodeEventType.COMMAND, DecodeEventType.EMERGENCY,
|
||||
DecodeEventType.NOTIFICATION, DecodeEventType.FUNCTION);
|
||||
|
||||
/**
|
||||
* Data call event types for filtering
|
||||
*/
|
||||
public static final EnumSet<DecodeEventType> DATA_CALLS = EnumSet.of(DecodeEventType.DATA_CALL,
|
||||
DecodeEventType.DATA_CALL_ENCRYPTED, DecodeEventType.DATA_PACKET, DecodeEventType.GPS,
|
||||
DecodeEventType.IP_PACKET, DecodeEventType.UDP_PACKET, DecodeEventType.SDM, DecodeEventType.ID_ANI,
|
||||
DecodeEventType.ID_UNIQUE);
|
||||
|
||||
/**
|
||||
* Registration event types for filtering
|
||||
*/
|
||||
public static final EnumSet<DecodeEventType> REGISTRATION = EnumSet.of(DecodeEventType.AFFILIATE,
|
||||
DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE, DecodeEventType.REGISTER, DecodeEventType.REGISTER_ESN,
|
||||
DecodeEventType.DEREGISTER, DecodeEventType.REQUEST, DecodeEventType.RESPONSE, DecodeEventType.RESPONSE_PACKET);
|
||||
|
||||
/**
|
||||
* All other event types of this enumeration that are not included in the groupings above.
|
||||
*/
|
||||
public static final EnumSet<DecodeEventType> OTHERS = EnumSet.copyOf(Arrays.stream(DecodeEventType.values())
|
||||
.filter(decodeEventType -> !decodeEventType.isGrouped()).toList());
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param label for the element
|
||||
*/
|
||||
DecodeEventType(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the enumeration element is contained in one of the enumset groupings above.
|
||||
* @return true if the element is grouped.
|
||||
*/
|
||||
public boolean isGrouped()
|
||||
{
|
||||
return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || COMMANDS.contains(this) ||
|
||||
DATA_CALLS.contains(this) || REGISTRATION.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Label or pretty value for the element
|
||||
* @return label
|
||||
*/
|
||||
public String getLabel()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses label as the default string value.
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event;
|
||||
|
||||
import io.github.dsheirer.filter.FilterEditor;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* History management panel with controls for managing item histories.
|
||||
*/
|
||||
public class HistoryManagementPanel<T> extends JPanel
|
||||
{
|
||||
private ClearableHistoryModel mModel;
|
||||
private FilterSet<T> mFilterSet;
|
||||
private FilterEditor<T> mFilterEditor;
|
||||
private JButton mClearButton;
|
||||
private JButton mFilterButton;
|
||||
private JSlider mHistorySlider;
|
||||
private JLabel mHistoryTitleLabel;
|
||||
private JLabel mHistoryValueLabel;
|
||||
private String mFilterEditorTitle;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param model to manage
|
||||
*/
|
||||
public HistoryManagementPanel(ClearableHistoryModel model, String filterEditorTitle)
|
||||
{
|
||||
mModel = model;
|
||||
mFilterEditorTitle = filterEditorTitle;
|
||||
setLayout(new MigLayout("insets 6 1 5 5", "[]5[]10[]5[]5[][grow]", ""));
|
||||
add(getFilterButton());
|
||||
add(getClearButton());
|
||||
add(getHistoryTitleLabel());
|
||||
add(getHistorySlider());
|
||||
add(getHistoryValueLabel());
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the filter set
|
||||
* @param filterSet to use
|
||||
*/
|
||||
public void updateFilterSet(FilterSet<T> filterSet)
|
||||
{
|
||||
mFilterSet = filterSet;
|
||||
|
||||
if(mFilterEditor != null)
|
||||
{
|
||||
getFilterEditor().updateFilterSet(filterSet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the panel method to also set the enabled state for the child controls.
|
||||
* @param enabled true if this component should be enabled, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
super.setEnabled(enabled);
|
||||
getClearButton().setEnabled(enabled);
|
||||
getFilterButton().setEnabled(enabled);
|
||||
getHistoryValueLabel().setEnabled(enabled);
|
||||
getHistoryTitleLabel().setEnabled(enabled);
|
||||
getHistorySlider().setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter editor
|
||||
* @return editor lazily constructed
|
||||
*/
|
||||
private FilterEditor<T> getFilterEditor()
|
||||
{
|
||||
if(mFilterEditor == null)
|
||||
{
|
||||
mFilterEditor = new FilterEditor<>(mFilterEditorTitle, getFilterButton(), mFilterSet);
|
||||
}
|
||||
|
||||
return mFilterEditor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter button for accessing the filter editor
|
||||
* @return filter button
|
||||
*/
|
||||
private JButton getFilterButton()
|
||||
{
|
||||
if(mFilterButton == null)
|
||||
{
|
||||
mFilterButton = new JButton("Filters");
|
||||
mFilterButton.setToolTipText("Edit filters");
|
||||
mFilterButton.addActionListener(arg0 -> EventQueue.invokeLater(() -> getFilterEditor().setVisible(true)));
|
||||
}
|
||||
|
||||
return mFilterButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear button
|
||||
* @return clear button
|
||||
*/
|
||||
private JButton getClearButton()
|
||||
{
|
||||
if(mClearButton == null)
|
||||
{
|
||||
mClearButton = new JButton("Clear");
|
||||
mClearButton.addActionListener(e -> mModel.clear());
|
||||
mClearButton.setToolTipText("Clears the history");
|
||||
}
|
||||
|
||||
return mClearButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* History value label.
|
||||
* @return label
|
||||
*/
|
||||
private JLabel getHistoryValueLabel()
|
||||
{
|
||||
if(mHistoryValueLabel == null)
|
||||
{
|
||||
mHistoryValueLabel = new JLabel(String.valueOf(ClearableHistoryModel.DEFAULT_HISTORY_SIZE));
|
||||
}
|
||||
|
||||
return mHistoryValueLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* History title label
|
||||
* @return label
|
||||
*/
|
||||
private JLabel getHistoryTitleLabel()
|
||||
{
|
||||
if(mHistoryTitleLabel == null)
|
||||
{
|
||||
mHistoryTitleLabel = new JLabel("History:");
|
||||
}
|
||||
|
||||
return mHistoryTitleLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* History value slider control
|
||||
* @return slider
|
||||
*/
|
||||
private JSlider getHistorySlider()
|
||||
{
|
||||
if(mHistorySlider == null)
|
||||
{
|
||||
mHistorySlider = new JSlider();
|
||||
mHistorySlider.setToolTipText("Adjust history size. Double-click to reset to default 200");
|
||||
mHistorySlider.setMinimum(0);
|
||||
mHistorySlider.setMaximum(2000);
|
||||
mHistorySlider.setMinorTickSpacing(25);
|
||||
mHistorySlider.setMajorTickSpacing(500);
|
||||
mHistorySlider.setPaintTicks(false);
|
||||
mHistorySlider.setPaintLabels(false);
|
||||
mHistorySlider.addMouseListener(new MouseListener()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent arg0)
|
||||
{
|
||||
if(SwingUtilities.isLeftMouseButton(arg0) && arg0.getClickCount() == 2)
|
||||
{
|
||||
mHistorySlider.setValue(ClearableHistoryModel.DEFAULT_HISTORY_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseEntered(MouseEvent arg0) {}
|
||||
public void mouseExited(MouseEvent arg0) {}
|
||||
public void mousePressed(MouseEvent arg0) {}
|
||||
public void mouseReleased(MouseEvent arg0) {}
|
||||
});
|
||||
|
||||
mHistorySlider.setValue(mModel.getHistorySize());
|
||||
mHistorySlider.addChangeListener(e -> {
|
||||
mModel.setHistorySize(mHistorySlider.getValue());
|
||||
getHistoryValueLabel().setText(String.valueOf(mHistorySlider.getValue()));
|
||||
});
|
||||
}
|
||||
|
||||
return mHistorySlider;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -48,11 +48,6 @@ public interface IDecodeEvent
|
||||
*/
|
||||
long getDuration();
|
||||
|
||||
/**
|
||||
* A description of the event or event type
|
||||
*/
|
||||
String getEventDescription();
|
||||
|
||||
/**
|
||||
* Collection of identifiers associated with the event. This collection should contain a
|
||||
* Role.FROM and a Role.TO identifier, a Decoder Type identifier, and (optionally) an Alias List
|
||||
@@ -86,7 +81,7 @@ public interface IDecodeEvent
|
||||
* Timeslot for the event.
|
||||
* @return timeslot or default of 0
|
||||
*/
|
||||
Integer getTimeslot();
|
||||
int getTimeslot();
|
||||
|
||||
/**
|
||||
* Indicates if the event has a timeslot specified
|
||||
|
||||
@@ -18,19 +18,16 @@
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.event;
|
||||
|
||||
import io.github.dsheirer.filter.AllPassFilter;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.StuffBitsMessage;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.awt.EventQueue;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
public class MessageActivityModel extends AbstractTableModel implements Listener<IMessage>
|
||||
/**
|
||||
* Table Model for decoded IMessages.
|
||||
*/
|
||||
public class MessageActivityModel extends ClearableHistoryModel<MessageItem> implements Listener<IMessage>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int TIME = 0;
|
||||
@@ -38,95 +35,20 @@ public class MessageActivityModel extends AbstractTableModel implements Listener
|
||||
private static final int TIMESLOT = 2;
|
||||
private static final int MESSAGE = 3;
|
||||
|
||||
protected int mMaxMessages = 200;
|
||||
protected LinkedList<MessageItem> mMessageItems = new LinkedList<>();
|
||||
protected int[] mColumnWidths = {20, 20, 500};
|
||||
protected String[] mHeaders = new String[]{"Time", "Protocol", "Timeslot", "Message"};
|
||||
|
||||
private String[] mHeaders = new String[]{"Time", "Protocol", "Timeslot", "Message"};
|
||||
private SimpleDateFormat mSDFTime = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
|
||||
|
||||
private FilterSet<IMessage> mMessageFilterSet = new FilterSet<>(new AllPassFilter<>());
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public MessageActivityModel()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the filter set
|
||||
* Implements the listener interface and wraps the IMessage in table-compatible message item wrapper.
|
||||
* @param message to add to the model
|
||||
*/
|
||||
public void setFilters(FilterSet filterSet)
|
||||
{
|
||||
mMessageFilterSet = filterSet;
|
||||
}
|
||||
|
||||
public void clearFilters()
|
||||
{
|
||||
mMessageFilterSet = new FilterSet<>(new AllPassFilter<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all messages from history
|
||||
*/
|
||||
public void clear()
|
||||
{
|
||||
EventQueue.invokeLater(() -> {
|
||||
mMessageItems.clear();
|
||||
fireTableDataChanged();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current messages and loads the messages argument
|
||||
*/
|
||||
public void clearAndSet(List<IMessage> messages)
|
||||
{
|
||||
EventQueue.invokeLater(() -> {
|
||||
mMessageItems.clear();
|
||||
fireTableDataChanged();
|
||||
for(IMessage message: messages)
|
||||
{
|
||||
receive(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public FilterSet<IMessage> getMessageFilterSet()
|
||||
{
|
||||
return mMessageFilterSet;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mMessageItems.clear();
|
||||
}
|
||||
|
||||
public int[] getColumnWidths()
|
||||
{
|
||||
return mColumnWidths;
|
||||
}
|
||||
|
||||
public void setColumnWidths(int[] widths)
|
||||
{
|
||||
if(widths.length != 3)
|
||||
{
|
||||
throw new IllegalArgumentException("MessageActivityModel - column widths array should have 3 elements");
|
||||
}
|
||||
else
|
||||
{
|
||||
mColumnWidths = widths;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxMessageCount()
|
||||
{
|
||||
return mMaxMessages;
|
||||
}
|
||||
|
||||
public void setMaxMessageCount(int count)
|
||||
{
|
||||
mMaxMessages = count;
|
||||
}
|
||||
|
||||
public void receive(final IMessage message)
|
||||
{
|
||||
//Don't process tail bits or stuff bits message fragments
|
||||
@@ -135,39 +57,7 @@ public class MessageActivityModel extends AbstractTableModel implements Listener
|
||||
return;
|
||||
}
|
||||
|
||||
if(mMessageFilterSet.passes(message))
|
||||
{
|
||||
final MessageItem messageItem = new MessageItem(message);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
mMessageItems.addFirst(messageItem);
|
||||
|
||||
MessageActivityModel.this.fireTableRowsInserted(0, 0);
|
||||
|
||||
prune();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void prune()
|
||||
{
|
||||
while(mMessageItems.size() > mMaxMessages)
|
||||
{
|
||||
MessageItem removed = mMessageItems.removeLast();
|
||||
removed.dispose();
|
||||
super.fireTableRowsDeleted(mMessageItems.size() - 1, mMessageItems.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mMessageItems.size();
|
||||
EventQueue.invokeLater(() -> add(new MessageItem(message)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -184,20 +74,20 @@ public class MessageActivityModel extends AbstractTableModel implements Listener
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
if(0 <= rowIndex && rowIndex < mMessageItems.size())
|
||||
{
|
||||
MessageItem messageItem = mMessageItems.get(rowIndex);
|
||||
MessageItem item = getItem(rowIndex);
|
||||
|
||||
if(item != null)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case TIME:
|
||||
return messageItem.getTimestamp(mSDFTime);
|
||||
return item.getTimestamp(mSDFTime);
|
||||
case PROTOCOL:
|
||||
return messageItem.getProtocol();
|
||||
return item.getProtocol();
|
||||
case TIMESLOT:
|
||||
return messageItem.getTimeslot();
|
||||
return item.getTimeslot();
|
||||
case MESSAGE:
|
||||
return messageItem.getText();
|
||||
return item.getText();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,13 +14,10 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.event;
|
||||
|
||||
import com.jidesoft.swing.JideButton;
|
||||
import com.jidesoft.swing.JideSplitButton;
|
||||
import io.github.dsheirer.filter.FilterEditorPanel;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.MessageHistory;
|
||||
@@ -30,37 +26,35 @@ import io.github.dsheirer.module.decode.DecoderFactory;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import javax.swing.RowFilter;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
|
||||
/**
|
||||
* Panel to display decoded messages/activity.
|
||||
*/
|
||||
public class MessageActivityPanel extends JPanel implements Listener<ProcessingChain>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MessageActivityPanel.class);
|
||||
private static final String TABLE_PREFERENCE_KEY = "message.activity.panel";
|
||||
private static MessageActivityModel mMessageModel = new MessageActivityModel();
|
||||
private static final Logger mLog = LoggerFactory.getLogger(MessageActivityPanel.class);
|
||||
private final String TABLE_PREFERENCE_KEY = "message.activity.panel";
|
||||
private MessageActivityModel mMessageModel = new MessageActivityModel();
|
||||
private MessageHistory mCurrentMessageHistory;
|
||||
private JTable mTable = new JTable(mMessageModel);
|
||||
private TableRowSorter<TableModel> mTableRowSorter;
|
||||
private JTableColumnWidthMonitor mTableColumnWidthMonitor;
|
||||
private UserPreferences mUserPreferences;
|
||||
private MessageManagementPanel mManagementPanel = new MessageManagementPanel();
|
||||
private FilterSet<IMessage> mMessageFilterSet;
|
||||
private HistoryManagementPanel<IMessage> mHistoryManagementPanel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -69,13 +63,14 @@ public class MessageActivityPanel extends JPanel implements Listener<ProcessingC
|
||||
public MessageActivityPanel(UserPreferences userPreferences)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill]", "[]0[grow,fill]"));
|
||||
|
||||
add(mManagementPanel, "span,growx");
|
||||
|
||||
add(new JScrollPane(mTable), "span,grow");
|
||||
mTableRowSorter = new TableRowSorter<>(mMessageModel);
|
||||
mTableRowSorter.setRowFilter(new MessageRowFilter());
|
||||
mTable.setRowSorter(mTableRowSorter);
|
||||
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill]", "[]0[grow,fill]"));
|
||||
mHistoryManagementPanel = new HistoryManagementPanel<>(mMessageModel, "Message Filter Editor");
|
||||
add(mHistoryManagementPanel, "span,growx");
|
||||
add(new JScrollPane(mTable), "span,grow");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,182 +84,62 @@ public class MessageActivityPanel extends JPanel implements Listener<ProcessingC
|
||||
mCurrentMessageHistory.removeListener(mMessageModel);
|
||||
}
|
||||
|
||||
//Unregister from changes made to the filter set
|
||||
if(mMessageFilterSet != null)
|
||||
{
|
||||
mMessageFilterSet.register(null);
|
||||
}
|
||||
|
||||
if(processingChain != null)
|
||||
{
|
||||
mCurrentMessageHistory = processingChain.getMessageHistory();
|
||||
mMessageModel.setFilters(DecoderFactory.getMessageFilters(processingChain.getModules()));
|
||||
mMessageModel.clearAndSet(mCurrentMessageHistory.getItems());
|
||||
mMessageFilterSet = DecoderFactory.getMessageFilters(processingChain.getModules());
|
||||
//Register filter change listener to refresh the table any time the event filters are changed.
|
||||
mMessageFilterSet.register(() -> mMessageModel.fireTableDataChanged());
|
||||
if(mHistoryManagementPanel != null)
|
||||
{
|
||||
mHistoryManagementPanel.updateFilterSet(mMessageFilterSet);
|
||||
}
|
||||
|
||||
List<MessageItem> currentHistory = new ArrayList<>();
|
||||
for(IMessage message: mCurrentMessageHistory.getItems())
|
||||
{
|
||||
currentHistory.add(new MessageItem(message));
|
||||
}
|
||||
|
||||
mMessageModel.clearAndSet(currentHistory);
|
||||
mCurrentMessageHistory.addListener(mMessageModel);
|
||||
mManagementPanel.enableButtons();
|
||||
mHistoryManagementPanel.setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentMessageHistory = null;
|
||||
mMessageModel.clearFilters();
|
||||
mMessageFilterSet = null;
|
||||
mMessageModel.clear();
|
||||
mManagementPanel.disableButtons();
|
||||
mHistoryManagementPanel.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageManagementPanel extends JPanel
|
||||
/**
|
||||
* Row visibility filter for messages
|
||||
*/
|
||||
public class MessageRowFilter extends RowFilter<TableModel, Integer>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private MessageHistoryButton mHistoryButton = new MessageHistoryButton();
|
||||
private MessageFilterButton mFilterButton = new MessageFilterButton();
|
||||
|
||||
public MessageManagementPanel()
|
||||
@Override
|
||||
public boolean include(Entry<? extends TableModel, ? extends Integer> entry)
|
||||
{
|
||||
setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", ""));
|
||||
|
||||
disableButtons();
|
||||
|
||||
add(mFilterButton);
|
||||
add(mHistoryButton);
|
||||
}
|
||||
|
||||
public void enableButtons()
|
||||
{
|
||||
mHistoryButton.setEnabled(true);
|
||||
mFilterButton.setEnabled(true);
|
||||
}
|
||||
|
||||
public void disableButtons()
|
||||
{
|
||||
mHistoryButton.setEnabled(false);
|
||||
mFilterButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageHistoryButton extends JideSplitButton
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MessageHistoryButton()
|
||||
{
|
||||
super("Clear");
|
||||
|
||||
JPanel historyPanel = new JPanel();
|
||||
|
||||
historyPanel.add(new JLabel("Message History:"));
|
||||
|
||||
final JSlider slider = new JSlider();
|
||||
slider.setMinimum(0);
|
||||
slider.setMaximum(2000);
|
||||
slider.setMajorTickSpacing(500);
|
||||
slider.setPaintTicks(true);
|
||||
slider.setPaintLabels(true);
|
||||
|
||||
slider.addMouseListener(new MouseListener()
|
||||
if(entry.getModel() instanceof MessageActivityModel model)
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent arg0)
|
||||
MessageItem item = model.getItem(entry.getIdentifier());
|
||||
|
||||
if(mMessageFilterSet != null && item != null && item.getMessage() != null)
|
||||
{
|
||||
if(SwingUtilities.isLeftMouseButton(arg0) && arg0.getClickCount() == 2)
|
||||
{
|
||||
slider.setValue(500); //default
|
||||
}
|
||||
IMessage message = item.getMessage();
|
||||
return mMessageFilterSet.canProcess(message) && mMessageFilterSet.passes(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseEntered(MouseEvent arg0)
|
||||
{
|
||||
}
|
||||
|
||||
public void mouseExited(MouseEvent arg0)
|
||||
{
|
||||
}
|
||||
|
||||
public void mousePressed(MouseEvent arg0)
|
||||
{
|
||||
}
|
||||
|
||||
public void mouseReleased(MouseEvent arg0)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
slider.setValue(((MessageActivityModel) mTable.getModel()).getMaxMessageCount());
|
||||
slider.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent arg0)
|
||||
{
|
||||
((MessageActivityModel) mTable.getModel()).setMaxMessageCount(slider.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
historyPanel.add(slider);
|
||||
|
||||
add(historyPanel);
|
||||
|
||||
/* Clear messages */
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
((MessageActivityModel) mTable.getModel()).clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class MessageFilterButton extends JideButton
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MessageFilterButton()
|
||||
{
|
||||
super("Filter");
|
||||
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
final JFrame editor = new JFrame();
|
||||
|
||||
editor.setTitle("Message Filter Editor");
|
||||
editor.setSize(600, 400);
|
||||
editor.setLocationRelativeTo(MessageFilterButton.this);
|
||||
editor.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
editor.setLayout(new MigLayout("", "[grow,fill]",
|
||||
"[grow,fill][][]"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
FilterSet<IMessage> filter = (FilterSet<IMessage>) ((MessageActivityModel) mTable.getModel())
|
||||
.getMessageFilterSet();
|
||||
|
||||
FilterEditorPanel<IMessage> panel = new FilterEditorPanel<>(filter);
|
||||
|
||||
JScrollPane scroller = new JScrollPane(panel);
|
||||
scroller.setViewportView(panel);
|
||||
|
||||
editor.add(scroller, "wrap");
|
||||
|
||||
editor.add(new JLabel("Right-click to select/deselect all nodes"), "wrap");
|
||||
|
||||
JButton close = new JButton("Close");
|
||||
close.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
editor.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
editor.add(close);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
editor.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event;
|
||||
@@ -31,19 +30,20 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
private double mHeading;
|
||||
private double mSpeed;
|
||||
|
||||
public PlottableDecodeEvent(long start)
|
||||
public PlottableDecodeEvent(DecodeEventType decodeEventType, long start)
|
||||
{
|
||||
super(start);
|
||||
super(decodeEventType, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decode event builder with the specified start timestamp.
|
||||
* @param decodeEventType for the event
|
||||
* @param timeStart for the event
|
||||
* @return builder
|
||||
*/
|
||||
public static PlottableDecodeEventBuilder plottableBuilder(long timeStart)
|
||||
public static PlottableDecodeEventBuilder plottableBuilder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
return new PlottableDecodeEventBuilder(timeStart);
|
||||
return new PlottableDecodeEventBuilder(decodeEventType, timeStart);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +102,7 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
{
|
||||
private long mTimeStart;
|
||||
private long mDuration;
|
||||
private String mEventDescription;
|
||||
private DecodeEventType mDecodeEventType;
|
||||
private IdentifierCollection mIdentifierCollection;
|
||||
private IChannelDescriptor mChannelDescriptor;
|
||||
private String mDetails;
|
||||
@@ -114,8 +114,9 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
/**
|
||||
* Constructs a builder instance with the specified start time in milliseconds
|
||||
*/
|
||||
public PlottableDecodeEventBuilder(long timeStart)
|
||||
public PlottableDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
mDecodeEventType = decodeEventType;
|
||||
mTimeStart = timeStart;
|
||||
}
|
||||
|
||||
@@ -149,16 +150,6 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the event description text
|
||||
* @param description of the event
|
||||
*/
|
||||
public PlottableDecodeEventBuilder eventDescription(String description)
|
||||
{
|
||||
mEventDescription = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identifier collection.
|
||||
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
|
||||
@@ -222,11 +213,10 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
*/
|
||||
public PlottableDecodeEvent build()
|
||||
{
|
||||
PlottableDecodeEvent decodeEvent = new PlottableDecodeEvent(mTimeStart);
|
||||
PlottableDecodeEvent decodeEvent = new PlottableDecodeEvent(mDecodeEventType, mTimeStart);
|
||||
decodeEvent.setChannelDescriptor(mChannelDescriptor);
|
||||
decodeEvent.setDetails(mDetails);
|
||||
decodeEvent.setDuration(mDuration);
|
||||
decodeEvent.setEventDescription(mEventDescription);
|
||||
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
||||
decodeEvent.setProtocol(mProtocol);
|
||||
decodeEvent.setLocation(mGeoPosition);
|
||||
@@ -235,5 +225,4 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
return decodeEvent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
|
||||
/**
|
||||
* Event filter for decode event types that are not specified in other event filters.
|
||||
*
|
||||
* This is intended as a catch-all for any elements of the DecodeEventType enumeration that may get added in the
|
||||
* future and are not deliberately added to the various event filter groupings listed in the enumeration.
|
||||
*/
|
||||
public class AllOtherEventFilter extends EventFilter
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public AllOtherEventFilter()
|
||||
{
|
||||
super("Other", DecodeEventType.OTHERS);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,40 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
|
||||
public class DecodeEventFilterSet extends FilterSet<IDecodeEvent> {
|
||||
public DecodeEventFilterSet() {
|
||||
super ("All Messages");
|
||||
|
||||
/**
|
||||
* Top-level decode event filter set.
|
||||
*/
|
||||
public class DecodeEventFilterSet extends FilterSet<IDecodeEvent>
|
||||
{
|
||||
public DecodeEventFilterSet()
|
||||
{
|
||||
super ("All Events");
|
||||
addFilter(new DecodedCallEventFilter());
|
||||
addFilter(new DecodedCallEncryptedEventFilter());
|
||||
addFilter(new DecodedDataEventFilter());
|
||||
addFilter(new DecodedCommandEventFilter());
|
||||
addFilter(new DecodedRegistrationEventFilter());
|
||||
addFilter(new AllOtherEventFilter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Encrypted call events filter
|
||||
*/
|
||||
public class DecodedCallEncryptedEventFilter extends EventFilter
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DecodedCallEncryptedEventFilter()
|
||||
{
|
||||
super("Voice Calls - Encrypted", Arrays.asList(
|
||||
DecodeEventType.CALL_ENCRYPTED,
|
||||
DecodeEventType.CALL_GROUP_ENCRYPTED,
|
||||
DecodeEventType.CALL_PATCH_GROUP_ENCRYPTED,
|
||||
DecodeEventType.CALL_INTERCONNECT_ENCRYPTED,
|
||||
DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED
|
||||
));
|
||||
super("Voice Calls - Encrypted", DecodeEventType.VOICE_CALLS_ENCRYPTED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Call events filter
|
||||
*/
|
||||
public class DecodedCallEventFilter extends EventFilter
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DecodedCallEventFilter()
|
||||
{
|
||||
super("Voice Calls", Arrays.asList(
|
||||
DecodeEventType.CALL_GROUP,
|
||||
DecodeEventType.CALL_PATCH_GROUP,
|
||||
DecodeEventType.CALL_ALERT,
|
||||
DecodeEventType.CALL_DETECT,
|
||||
DecodeEventType.CALL_DO_NOT_MONITOR,
|
||||
DecodeEventType.CALL_END,
|
||||
DecodeEventType.CALL_INTERCONNECT,
|
||||
DecodeEventType.CALL_UNIQUE_ID,
|
||||
DecodeEventType.CALL_UNIT_TO_UNIT,
|
||||
DecodeEventType.CALL_NO_TUNER,
|
||||
DecodeEventType.CALL_TIMEOUT
|
||||
));
|
||||
super("Voice Calls", DecodeEventType.VOICE_CALLS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Event filter for network commands.
|
||||
*/
|
||||
public class DecodedCommandEventFilter extends EventFilter
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DecodedCommandEventFilter()
|
||||
{
|
||||
super("Commands", Arrays.asList(
|
||||
DecodeEventType.ANNOUNCEMENT,
|
||||
DecodeEventType.STATION_ID,
|
||||
DecodeEventType.PAGE,
|
||||
DecodeEventType.QUERY,
|
||||
DecodeEventType.RADIO_CHECK,
|
||||
DecodeEventType.STATUS,
|
||||
DecodeEventType.COMMAND,
|
||||
DecodeEventType.EMERGENCY,
|
||||
DecodeEventType.NOTIFICATION,
|
||||
DecodeEventType.FUNCTION
|
||||
));
|
||||
super("Commands", DecodeEventType.COMMANDS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Event filter for data call event types
|
||||
*/
|
||||
public class DecodedDataEventFilter extends EventFilter
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DecodedDataEventFilter()
|
||||
{
|
||||
super("Data Calls", Arrays.asList(
|
||||
DecodeEventType.DATA_CALL,
|
||||
DecodeEventType.DATA_CALL_ENCRYPTED,
|
||||
DecodeEventType.DATA_PACKET,
|
||||
DecodeEventType.GPS,
|
||||
DecodeEventType.IP_PACKET,
|
||||
DecodeEventType.UDP_PACKET,
|
||||
DecodeEventType.SDM,
|
||||
DecodeEventType.ID_ANI,
|
||||
DecodeEventType.ID_UNIQUE
|
||||
));
|
||||
super("Data Calls", DecodeEventType.DATA_CALLS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Event filter for registration related events
|
||||
*/
|
||||
public class DecodedRegistrationEventFilter extends EventFilter
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public DecodedRegistrationEventFilter()
|
||||
{
|
||||
super("Registrations", Arrays.asList(
|
||||
DecodeEventType.AFFILIATE,
|
||||
DecodeEventType.AUTOMATIC_REGISTRATION_SERVICE,
|
||||
DecodeEventType.REGISTER,
|
||||
DecodeEventType.REGISTER_ESN,
|
||||
DecodeEventType.DEREGISTER,
|
||||
DecodeEventType.REQUEST,
|
||||
DecodeEventType.RESPONSE,
|
||||
DecodeEventType.RESPONSE_PACKET
|
||||
));
|
||||
super("Registrations", DecodeEventType.REGISTRATION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter;
|
||||
|
||||
import com.jidesoft.swing.JideSplitButton;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
public class EventClearButton extends JideSplitButton
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -21,37 +39,29 @@ public class EventClearButton extends JideSplitButton
|
||||
|
||||
JPanel historyPanel = new JPanel();
|
||||
|
||||
historyPanel.add(new JLabel("History Entries:"));
|
||||
|
||||
historyPanel.add(new JLabel("History Size:"));
|
||||
final JSlider historySlider = initializeHistorySlider();
|
||||
JLabel valueLabel = new JLabel(String.valueOf(maxHistoryCount));
|
||||
|
||||
historySlider.setValue(maxHistoryCount);
|
||||
historySlider.addChangeListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent arg0)
|
||||
historySlider.addChangeListener(arg0 -> {
|
||||
if (mEventClearHandler != null)
|
||||
{
|
||||
if (mEventClearHandler != null)
|
||||
{
|
||||
mEventClearHandler.onHistoryLimitChanged(historySlider.getValue());
|
||||
}
|
||||
mEventClearHandler.onHistoryLimitChanged(historySlider.getValue());
|
||||
}
|
||||
|
||||
valueLabel.setText(String.valueOf(historySlider.getValue()));
|
||||
});
|
||||
|
||||
historyPanel.add(historySlider);
|
||||
|
||||
historyPanel.add(valueLabel);
|
||||
add(historyPanel);
|
||||
|
||||
/* This handles the click action on the main button. Clear messages */
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
addActionListener(e -> {
|
||||
if (mEventClearHandler != null)
|
||||
{
|
||||
if (mEventClearHandler != null)
|
||||
{
|
||||
mEventClearHandler.onClearHistoryClicked();
|
||||
}
|
||||
mEventClearHandler.onClearHistoryClicked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
public interface EventClearHandler {
|
||||
/**
|
||||
* Handler interface for changes to the event history size or content.
|
||||
*/
|
||||
public interface EventClearHandler
|
||||
{
|
||||
/**
|
||||
* Invoked when the retained history size limit is changed
|
||||
* @param newHistoryLimit new size
|
||||
*/
|
||||
void onHistoryLimitChanged(int newHistoryLimit);
|
||||
|
||||
/**
|
||||
* Invoked when the history is cleared
|
||||
*/
|
||||
void onClearHistoryClicked();
|
||||
}
|
||||
|
||||
@@ -1,56 +1,74 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.event.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* This is used as a base for {@link IDecodeEvent} types.
|
||||
* This will take a list of {@link IDecodeEvent}s and generate filters for the list.
|
||||
* This will default canProcess to TRUE. Override if other value or evaluation is needed.
|
||||
*
|
||||
*/
|
||||
public class EventFilter extends Filter<IDecodeEvent>
|
||||
public class EventFilter extends Filter<IDecodeEvent,DecodeEventType>
|
||||
{
|
||||
private Map<DecodeEventType, FilterElement<DecodeEventType>> mElements = new EnumMap<>(DecodeEventType.class);
|
||||
private EventKeyExtractor mKeyExtractor = new EventKeyExtractor();
|
||||
|
||||
public EventFilter(String name, List<DecodeEventType> eventTypes)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param name of this filter
|
||||
* @param decodeEventTypes to filter against
|
||||
*/
|
||||
public EventFilter(String name, Collection<DecodeEventType> decodeEventTypes)
|
||||
{
|
||||
super(name);
|
||||
|
||||
for (DecodeEventType eventType : eventTypes) {
|
||||
mElements.put(eventType, new FilterElement<>(eventType));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IDecodeEvent iDecodeEvent)
|
||||
{
|
||||
if (mEnabled && canProcess(iDecodeEvent))
|
||||
for (DecodeEventType decodeEventType : decodeEventTypes)
|
||||
{
|
||||
if (mElements.containsKey(iDecodeEvent.getEventType()))
|
||||
{
|
||||
return mElements.get(iDecodeEvent.getEventType()).isEnabled();
|
||||
}
|
||||
add(new FilterElement<>(decodeEventType));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor function
|
||||
* @return function.
|
||||
*/
|
||||
@Override
|
||||
public boolean canProcess(IDecodeEvent iDecodeEvent)
|
||||
public Function<IDecodeEvent, DecodeEventType> getKeyExtractor()
|
||||
{
|
||||
return true;
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
/**
|
||||
* Key extractor function for decode events to extract the decode event type
|
||||
*/
|
||||
public static class EventKeyExtractor implements Function<IDecodeEvent,DecodeEventType>
|
||||
{
|
||||
return new ArrayList<>(mElements.values());
|
||||
@Override
|
||||
public DecodeEventType apply(IDecodeEvent iDecodeEvent)
|
||||
{
|
||||
return iDecodeEvent.getEventType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,103 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter;
|
||||
|
||||
import com.jidesoft.swing.JideButton;
|
||||
import io.github.dsheirer.filter.FilterEditorPanel;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
/**
|
||||
* Event filter button that includes split button functionality to allow user to select filter items.
|
||||
*
|
||||
* @param <T> type of filter.
|
||||
*/
|
||||
public class EventFilterButton<T> extends JideButton
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param dialogTitle for the dialog/panel that appears
|
||||
* @param filterSet to include in the editor panel.
|
||||
*/
|
||||
public EventFilterButton(String dialogTitle, FilterSet<T> filterSet)
|
||||
{
|
||||
this("Filter", dialogTitle, filterSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param buttonLabel to use on the button
|
||||
* @param dialogTitle for the dialog/panel that appears
|
||||
* @param filterSet to include in the editor panel.
|
||||
*/
|
||||
public EventFilterButton(String buttonLabel, String dialogTitle, FilterSet<T> filterSet)
|
||||
{
|
||||
super(buttonLabel);
|
||||
|
||||
addActionListener(
|
||||
new EventFilterActionHandler(dialogTitle, filterSet)
|
||||
);
|
||||
addActionListener(new EventFilterActionHandler(dialogTitle, filterSet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Action handler for the button
|
||||
*/
|
||||
public class EventFilterActionHandler implements ActionListener
|
||||
{
|
||||
private String title;
|
||||
private FilterSet<T> filterSet;
|
||||
private String mTitle;
|
||||
private FilterSet<T> mFilterSet;
|
||||
|
||||
public EventFilterActionHandler( String title, FilterSet<T> filterSet)
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param title for this panel
|
||||
* @param filterSet to edit
|
||||
*/
|
||||
public EventFilterActionHandler(String title, FilterSet<T> filterSet)
|
||||
{
|
||||
this.title = title;
|
||||
this.filterSet = filterSet;
|
||||
mTitle = title;
|
||||
mFilterSet = filterSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
final JFrame editor = new JFrame();
|
||||
|
||||
editor.setTitle(title);
|
||||
editor.setTitle(mTitle);
|
||||
editor.setLocationRelativeTo(EventFilterButton.this);
|
||||
editor.setSize(600, 400);
|
||||
editor.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
editor.setLayout(new MigLayout("", "[grow,fill]",
|
||||
"[grow,fill][][]"));
|
||||
|
||||
FilterEditorPanel<T> panel = new FilterEditorPanel<T>(filterSet);
|
||||
|
||||
editor.setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][][]"));
|
||||
FilterEditorPanel<T> panel = new FilterEditorPanel<T>(mFilterSet);
|
||||
JScrollPane scroller = new JScrollPane(panel);
|
||||
scroller.setViewportView(panel);
|
||||
|
||||
editor.add(scroller, "wrap");
|
||||
|
||||
editor.add(new JLabel("Right-click to select/deselect all nodes"), "wrap");
|
||||
|
||||
JButton close = new JButton("Close");
|
||||
close.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
editor.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
close.addActionListener(e1 -> editor.dispose());
|
||||
editor.add(close);
|
||||
|
||||
EventQueue.invokeLater(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
editor.setVisible(true);
|
||||
}
|
||||
});
|
||||
EventQueue.invokeLater(() -> editor.setVisible(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package io.github.dsheirer.module.decode.event.filter;
|
||||
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
|
||||
public interface EventFilterProvider<T> {
|
||||
FilterSet<T> getFilterSet();
|
||||
void setFilterSet(FilterSet<T> filterSet);
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.fleetsync2;
|
||||
|
||||
@@ -33,13 +30,16 @@ import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.identifier.FleetsyncIdentifier;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.message.Fleetsync2Message;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Fleetsync2DecoderState extends DecoderState
|
||||
{
|
||||
private Set<FleetsyncIdentifier> mIdents = new TreeSet<>();
|
||||
private Map<FleetsyncIdentifier,Integer> mFromIdentCountsMap = new HashMap<>();
|
||||
private Map<FleetsyncIdentifier,Integer> mToIdentCountsMap = new HashMap<>();
|
||||
|
||||
public Fleetsync2DecoderState()
|
||||
{
|
||||
@@ -52,16 +52,6 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
return DecoderType.FLEETSYNC2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the overall decoder state and clears any accumulated event details
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
super.reset();
|
||||
mIdents.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
@@ -87,21 +77,37 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
|
||||
getIdentifierCollection().update(fleetsync.getIdentifiers());
|
||||
|
||||
mIdents.add(fleetsync.getFromIdentifier());
|
||||
mIdents.add(fleetsync.getToIdentifier());
|
||||
FleetsyncIdentifier from = fleetsync.getFromIdentifier();
|
||||
|
||||
if(mFromIdentCountsMap.containsKey(from))
|
||||
{
|
||||
mFromIdentCountsMap.put(from, mFromIdentCountsMap.get(from) + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
mFromIdentCountsMap.put(from, 1);
|
||||
}
|
||||
|
||||
FleetsyncIdentifier to = fleetsync.getToIdentifier();
|
||||
|
||||
if(to != null)
|
||||
{
|
||||
if(mToIdentCountsMap.containsKey(to))
|
||||
{
|
||||
mToIdentCountsMap.put(to, mToIdentCountsMap.get(to) + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
mToIdentCountsMap.put(to, 1);
|
||||
}
|
||||
}
|
||||
|
||||
switch(fleetsync.getMessageType())
|
||||
{
|
||||
case ANI:
|
||||
case EMERGENCY:
|
||||
case LONE_WORKER_EMERGENCY:
|
||||
DecodeEvent aniEvent = DecodeEvent.builder(fleetsync.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.eventDescription(fleetsync.getMessageType().toString())
|
||||
.details(fleetsync.toString())
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.build();
|
||||
|
||||
DecodeEvent aniEvent = getDecodeEvent(fleetsync, getDecodeEventType(fleetsync.getMessageType()));
|
||||
broadcast(aniEvent);
|
||||
broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.CALL));
|
||||
break;
|
||||
@@ -109,19 +115,13 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
case PAGING:
|
||||
case STATUS:
|
||||
case UNKNOWN:
|
||||
DecodeEvent statusEvent = DecodeEvent.builder(fleetsync.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.eventDescription(fleetsync.getMessageType().toString())
|
||||
.details(fleetsync.toString())
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.build();
|
||||
DecodeEvent statusEvent = getDecodeEvent(fleetsync, getDecodeEventType(fleetsync.getMessageType()));
|
||||
broadcast(statusEvent);
|
||||
broadcast(new DecoderStateEvent(this, DecoderStateEvent.Event.DECODE, State.DATA));
|
||||
break;
|
||||
case GPS:
|
||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(fleetsync.getTimestamp())
|
||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, fleetsync.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.eventDescription(DecodeEventType.GPS.toString())
|
||||
.details(fleetsync.toString())
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.protocol(Protocol.FLEETSYNC)
|
||||
@@ -135,6 +135,35 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
}
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(Fleetsync2Message fleetsync, DecodeEventType eventType) {
|
||||
return DecodeEvent.builder(eventType, fleetsync.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details(fleetsync.getMessageType() + " " + fleetsync)
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.protocol(Protocol.FLEETSYNC)
|
||||
.build();
|
||||
}
|
||||
|
||||
private DecodeEventType getDecodeEventType(FleetsyncMessageType fleetsyncMessageType) {
|
||||
switch (fleetsyncMessageType) {
|
||||
case ANI:
|
||||
return DecodeEventType.ID_ANI;
|
||||
case EMERGENCY:
|
||||
case LONE_WORKER_EMERGENCY:
|
||||
return DecodeEventType.EMERGENCY;
|
||||
case ACKNOWLEDGE:
|
||||
return DecodeEventType.ACKNOWLEDGE;
|
||||
case PAGING:
|
||||
return DecodeEventType.PAGE;
|
||||
case STATUS:
|
||||
return DecodeEventType.STATUS;
|
||||
case GPS:
|
||||
return DecodeEventType.GPS;
|
||||
default:
|
||||
return DecodeEventType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to reset events issued by the channel state
|
||||
*/
|
||||
@@ -158,17 +187,30 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
|
||||
sb.append("=============================\n");
|
||||
sb.append("Decoder:\tFleetsync II\n\n");
|
||||
sb.append("Fleetsync Idents\n");
|
||||
|
||||
if(mIdents.isEmpty())
|
||||
if(mFromIdentCountsMap.isEmpty() && mToIdentCountsMap.isEmpty())
|
||||
{
|
||||
sb.append("Fleetsync Idents\n");
|
||||
sb.append(" None\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Fleetsync From Idents\n");
|
||||
|
||||
for (FleetsyncIdentifier mIdent : mIdents) {
|
||||
sb.append(" ").append(mIdent.formatted()).append("\n");
|
||||
List<FleetsyncIdentifier> fromIdents = new ArrayList<>(mFromIdentCountsMap.keySet());
|
||||
Collections.sort(fromIdents);
|
||||
for(FleetsyncIdentifier from: fromIdents)
|
||||
{
|
||||
sb.append(" ").append(from.formatted()).append(" - Count:").append(mFromIdentCountsMap.get(from)).append("\n");
|
||||
}
|
||||
|
||||
sb.append("\nFleetsync To Idents\n");
|
||||
|
||||
List<FleetsyncIdentifier> toIdents = new ArrayList<>(mToIdentCountsMap.keySet());
|
||||
Collections.sort(toIdents);
|
||||
for(FleetsyncIdentifier to: toIdents)
|
||||
{
|
||||
sb.append(" ").append(to.formatted()).append(" - Count:").append(mToIdentCountsMap.get(to)).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.fleetsync2;
|
||||
@@ -24,52 +23,63 @@ import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.message.Fleetsync2Message;
|
||||
import io.github.dsheirer.module.decode.ltrnet.LtrNetMessageType;
|
||||
import java.util.function.Function;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class FleetsyncMessageFilter extends Filter<IMessage>
|
||||
/**
|
||||
* Filter for Fleetsync messages.
|
||||
*/
|
||||
public class FleetsyncMessageFilter extends Filter<IMessage,FleetsyncMessageType>
|
||||
{
|
||||
private Map<FleetsyncMessageType,FilterElement<FleetsyncMessageType>> mElements = new EnumMap<>(FleetsyncMessageType.class);
|
||||
private final KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public FleetsyncMessageFilter()
|
||||
{
|
||||
super("Fleetsync Message Filter");
|
||||
super("Fleetsync Messages");
|
||||
|
||||
for(FleetsyncMessageType type : FleetsyncMessageType.values())
|
||||
{
|
||||
if(type != FleetsyncMessageType.UNKNOWN)
|
||||
{
|
||||
mElements.put(type, new FilterElement<FleetsyncMessageType>(type));
|
||||
}
|
||||
add(new FilterElement<>(type));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IMessage message)
|
||||
{
|
||||
if(mEnabled && canProcess(message))
|
||||
{
|
||||
Fleetsync2Message fleet = (Fleetsync2Message)message;
|
||||
|
||||
if(mElements.containsKey(fleet.getMessageType()))
|
||||
{
|
||||
return mElements.get(fleet.getMessageType()).isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits processing to only fleetsync messages.
|
||||
* @param message to test
|
||||
* @return true if message type is correct.
|
||||
*/
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof Fleetsync2Message;
|
||||
return message instanceof Fleetsync2Message && super.canProcess(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor for fleetsync messages
|
||||
* @return key extractor
|
||||
*/
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
public Function<IMessage, FleetsyncMessageType> getKeyExtractor()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>(mElements.values());
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor for Fleetsync 2 Messages.
|
||||
*/
|
||||
private class KeyExtractor implements Function<IMessage, FleetsyncMessageType>
|
||||
{
|
||||
@Override
|
||||
public FleetsyncMessageType apply(IMessage message)
|
||||
{
|
||||
if(message instanceof Fleetsync2Message f2m)
|
||||
{
|
||||
return f2m.getMessageType();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.lj1200;
|
||||
|
||||
@@ -30,14 +27,15 @@ import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LJ1200DecoderState extends DecoderState
|
||||
{
|
||||
@@ -72,11 +70,11 @@ public class LJ1200DecoderState extends DecoderState
|
||||
ic.remove(IdentifierClass.USER);
|
||||
ic.update(lj.getIdentifiers());
|
||||
|
||||
DecodeEvent event = DecodeEvent.builder(System.currentTimeMillis())
|
||||
DecodeEvent event = DecodeEvent.builder(DecodeEventType.DATA_PACKET, System.currentTimeMillis())
|
||||
.protocol(Protocol.LOJACK)
|
||||
.identifiers(ic)
|
||||
.channel(getCurrentChannel())
|
||||
.details("LOJACK")
|
||||
.eventDescription(lj.toString())
|
||||
.details("LOJACK " + lj)
|
||||
.build();
|
||||
|
||||
broadcast(event);
|
||||
@@ -91,11 +89,11 @@ public class LJ1200DecoderState extends DecoderState
|
||||
ic.remove(IdentifierClass.USER);
|
||||
ic.update(transponder.getIdentifiers());
|
||||
|
||||
DecodeEvent transponderEvent = DecodeEvent.builder(System.currentTimeMillis())
|
||||
DecodeEvent transponderEvent = DecodeEvent.builder(DecodeEventType.GPS, System.currentTimeMillis())
|
||||
.protocol(Protocol.LOJACK)
|
||||
.identifiers(ic)
|
||||
.channel(getCurrentChannel())
|
||||
.details("LOJACK TRANSPONDER")
|
||||
.eventDescription(transponder.toString())
|
||||
.details("LOJACK TRANSPONDER " + transponder)
|
||||
.build();
|
||||
|
||||
broadcast(transponderEvent);
|
||||
@@ -114,7 +112,7 @@ public class LJ1200DecoderState extends DecoderState
|
||||
{
|
||||
sb.append("Transponder Addresses:\n");
|
||||
|
||||
List<String> addresses = new ArrayList<String>(mAddresses);
|
||||
List<String> addresses = new ArrayList<>(mAddresses);
|
||||
|
||||
Collections.sort(addresses);
|
||||
|
||||
|
||||
@@ -1,52 +1,66 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.lj1200;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import java.util.function.Function;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class LJ1200MessageFilter extends Filter<IMessage>
|
||||
/**
|
||||
* Filter for LJ1200 messages
|
||||
*/
|
||||
public class LJ1200MessageFilter extends Filter<IMessage,String>
|
||||
{
|
||||
private static final String LJ1200_KEY = "LJ-1200";
|
||||
private final LJ1200KeyExtractor mKeyExtractor = new LJ1200KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public LJ1200MessageFilter()
|
||||
{
|
||||
super("LJ-1200 Message Filter");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IMessage message)
|
||||
{
|
||||
return mEnabled && canProcess(message);
|
||||
super("LJ-1200 Messages");
|
||||
add(new FilterElement<>(LJ1200_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof LJ1200Message || message instanceof LJ1200TransponderMessage;
|
||||
return (message instanceof LJ1200Message || message instanceof LJ1200TransponderMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
public Function<IMessage, String> getKeyExtractor()
|
||||
{
|
||||
return Collections.EMPTY_LIST;
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor
|
||||
*/
|
||||
public class LJ1200KeyExtractor implements Function<IMessage,String>
|
||||
{
|
||||
@Override
|
||||
public String apply(IMessage message)
|
||||
{
|
||||
return LJ1200_KEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.ltrnet;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* LTRNet decode event
|
||||
*/
|
||||
public class LTRNetDecodeEvent extends DecodeEvent
|
||||
{
|
||||
/**
|
||||
* Constructs an LTR NET decode event
|
||||
* @param start
|
||||
*/
|
||||
public LTRNetDecodeEvent(DecodeEventType decodeEventType, long start)
|
||||
{
|
||||
super(decodeEventType, start);
|
||||
setProtocol(Protocol.LTR_NET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decode event builder with the specified start timestamp.
|
||||
* @param timeStart for the event
|
||||
* @return builder
|
||||
*/
|
||||
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
|
||||
decodeEventBuilder.protocol(Protocol.LTR_NET);
|
||||
return decodeEventBuilder;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,15 +20,21 @@ package io.github.dsheirer.module.decode.ltrnet;
|
||||
|
||||
import io.github.dsheirer.bits.MessageFramer;
|
||||
import io.github.dsheirer.bits.SyncPattern;
|
||||
import io.github.dsheirer.dsp.fsk.LTRDecoder;
|
||||
import io.github.dsheirer.message.MessageDirection;
|
||||
import io.github.dsheirer.module.decode.Decoder;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.dsp.fsk.LTRDecoder;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.real.IRealBufferListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* LTR-Net decoder
|
||||
*/
|
||||
public class LTRNetDecoder extends Decoder implements IRealBufferListener, Listener<float[]>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(LTRNetDecoder.class);
|
||||
public static final int LTR_NET_MESSAGE_LENGTH = 40;
|
||||
protected LTRDecoder mLTRDecoder;
|
||||
private MessageFramer mLTRMessageFramer;
|
||||
@@ -51,8 +57,12 @@ public class LTRNetDecoder extends Decoder implements IRealBufferListener, Liste
|
||||
mLTRMessageFramer = new MessageFramer(SyncPattern.LTR_STANDARD_ISW.getPattern(), LTR_NET_MESSAGE_LENGTH);
|
||||
}
|
||||
|
||||
mLTRDecoder.setMessageFramer(mLTRMessageFramer);
|
||||
mLTRMessageFramer.setSyncDetectListener(mLTRDecoder);
|
||||
mLTRDecoder.setListener(bits -> {
|
||||
for(boolean bit: bits)
|
||||
{
|
||||
mLTRMessageFramer.process(bit);
|
||||
}
|
||||
});
|
||||
mLTRMessageProcessor = new LTRNetMessageProcessor(config.getMessageDirection());
|
||||
mLTRMessageFramer.addMessageListener(mLTRMessageProcessor);
|
||||
mLTRMessageProcessor.setMessageListener(getMessageListener());
|
||||
@@ -64,7 +74,7 @@ public class LTRNetDecoder extends Decoder implements IRealBufferListener, Liste
|
||||
*/
|
||||
public LTRNetDecoder(DecodeConfigLTRNet config)
|
||||
{
|
||||
this(config, new LTRDecoder(LTR_NET_MESSAGE_LENGTH));
|
||||
this(config, new LTRDecoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,9 +90,9 @@ public class LTRNetDecoder extends Decoder implements IRealBufferListener, Liste
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] reusableFloatBuffer)
|
||||
public void receive(float[] demodulatedSamples)
|
||||
{
|
||||
mLTRDecoder.receive(reusableFloatBuffer);
|
||||
mLTRDecoder.receive(demodulatedSamples);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -27,7 +27,7 @@ public class LTRNetDecoderInstrumented extends LTRNetDecoder
|
||||
|
||||
public LTRNetDecoderInstrumented(DecodeConfigLTRNet config)
|
||||
{
|
||||
super(config, new LTRDecoderInstrumented(LTR_NET_MESSAGE_LENGTH));
|
||||
super(config, new LTRDecoderInstrumented());
|
||||
}
|
||||
|
||||
public LTRDecoderInstrumented getLTRDecoder()
|
||||
@@ -36,9 +36,9 @@ public class LTRNetDecoderInstrumented extends LTRNetDecoder
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(float[] realBuffer)
|
||||
public void receive(float[] demodulatedSamples)
|
||||
{
|
||||
super.receive(realBuffer);
|
||||
super.receive(demodulatedSamples);
|
||||
|
||||
bufferCount.setValue(bufferCount.intValue() + 1);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.ltrnet;
|
||||
|
||||
@@ -38,6 +35,7 @@ import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.MessageDirection;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.module.decode.ltrnet.channel.LtrNetChannel;
|
||||
import io.github.dsheirer.module.decode.ltrnet.identifier.LtrNetRadioIdentifier;
|
||||
import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage;
|
||||
@@ -60,9 +58,6 @@ import io.github.dsheirer.module.decode.ltrnet.message.osw.SystemIdle;
|
||||
import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyHigh;
|
||||
import io.github.dsheirer.module.decode.ltrnet.message.osw.TransmitFrequencyLow;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -71,6 +66,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LTRNetDecoderState extends DecoderState
|
||||
{
|
||||
@@ -166,10 +163,24 @@ public class LTRNetDecoderState extends DecoderState
|
||||
|
||||
if(mCurrentCallTalkgroup == null || !mCurrentCallTalkgroup.equals(talkgroup))
|
||||
{
|
||||
DecodeEventType decodeEventType = null;
|
||||
|
||||
if(talkgroup.getTalkgroup() == 253)
|
||||
{
|
||||
decodeEventType = DecodeEventType.REGISTER;
|
||||
}
|
||||
else if(talkgroup.getTalkgroup() == 254)
|
||||
{
|
||||
decodeEventType = DecodeEventType.STATION_ID;
|
||||
}
|
||||
else
|
||||
{
|
||||
decodeEventType = DecodeEventType.CALL;
|
||||
}
|
||||
|
||||
getIdentifierCollection().remove(IdentifierClass.USER);
|
||||
getIdentifierCollection().update(talkgroup);
|
||||
mCurrentCallEvent = DecodeEvent.builder(timestamp)
|
||||
.protocol(Protocol.LTR_NET)
|
||||
mCurrentCallEvent = LTRNetDecodeEvent.builder(decodeEventType, timestamp)
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.build();
|
||||
@@ -181,17 +192,14 @@ public class LTRNetDecoderState extends DecoderState
|
||||
|
||||
if(talkgroup.getTalkgroup() == 253)
|
||||
{
|
||||
mCurrentCallEvent.setEventDescription("Register");
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
|
||||
}
|
||||
else if(talkgroup.getTalkgroup() == 254)
|
||||
{
|
||||
mCurrentCallEvent.setEventDescription("FCC CWID");
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentCallEvent.setEventDescription("Call");
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.CALL));
|
||||
}
|
||||
|
||||
@@ -203,16 +211,7 @@ public class LTRNetDecoderState extends DecoderState
|
||||
|
||||
if(decodeEvent == null)
|
||||
{
|
||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
ic.remove(IdentifierClass.USER);
|
||||
ic.update(talkgroup);
|
||||
|
||||
decodeEvent = DecodeEvent.builder(timestamp)
|
||||
.eventDescription("Call Detect")
|
||||
.channel(mChannelMap.get(channel))
|
||||
.protocol(Protocol.LTR_NET)
|
||||
.identifiers(ic)
|
||||
.build();
|
||||
decodeEvent = getDecodeEvent(talkgroup, channel, timestamp, DecodeEventType.CALL_DETECT);
|
||||
mCallDetectMap.put(channel, decodeEvent);
|
||||
}
|
||||
else
|
||||
@@ -223,16 +222,7 @@ public class LTRNetDecoderState extends DecoderState
|
||||
if(eventTalkgroup == null || !eventTalkgroup.equals(talkgroup) ||
|
||||
(timestamp - decodeEvent.getTimeStart() - decodeEvent.getDuration() > 2000))
|
||||
{
|
||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
ic.remove(IdentifierClass.USER);
|
||||
ic.update(talkgroup);
|
||||
|
||||
decodeEvent = DecodeEvent.builder(timestamp)
|
||||
.eventDescription("Call Detect")
|
||||
.channel(mChannelMap.get(channel))
|
||||
.protocol(Protocol.LTR_NET)
|
||||
.identifiers(ic)
|
||||
.build();
|
||||
decodeEvent = getDecodeEvent(talkgroup, channel, timestamp, DecodeEventType.CALL_DETECT);
|
||||
mCallDetectMap.put(channel, decodeEvent);
|
||||
}
|
||||
}
|
||||
@@ -307,89 +297,59 @@ public class LTRNetDecoderState extends DecoderState
|
||||
switch(((LtrNetMessage)message).getLtrNetMessageType())
|
||||
{
|
||||
case ISW_CALL_END:
|
||||
if(message instanceof IswCallEnd)
|
||||
if(message instanceof IswCallEnd callEnd)
|
||||
{
|
||||
IswCallEnd callEnd = (IswCallEnd)message;
|
||||
mTalkgroups.add(callEnd.getTalkgroup());
|
||||
processCallEnd(callEnd.getChannel(), callEnd.getTalkgroup(), callEnd.getTimestamp());
|
||||
}
|
||||
break;
|
||||
case ISW_CALL_START:
|
||||
if(message instanceof IswCallStart)
|
||||
if(message instanceof IswCallStart callStart)
|
||||
{
|
||||
IswCallStart callStart = (IswCallStart)message;
|
||||
mTalkgroups.add(callStart.getTalkgroup());
|
||||
processCallStart(callStart.getChannel(), callStart.getTalkgroup(), callStart.getTimestamp(),
|
||||
MessageDirection.ISW);
|
||||
}
|
||||
break;
|
||||
case ISW_REGISTRATION_REQUEST_ESN_HIGH:
|
||||
if(message instanceof RegistrationRequestEsnHigh)
|
||||
if(message instanceof RegistrationRequestEsnHigh registrationRequestEsn)
|
||||
{
|
||||
RegistrationRequestEsnHigh registrationRequestEsn = (RegistrationRequestEsnHigh)message;
|
||||
|
||||
if(registrationRequestEsn.isCompleteEsn())
|
||||
{
|
||||
getIdentifierCollection().update(registrationRequestEsn.getESN());
|
||||
mESNIdentifiers.add(registrationRequestEsn.getESN());
|
||||
}
|
||||
|
||||
broadcast(DecodeEvent.builder(message.getTimestamp())
|
||||
.eventDescription("Registration Request")
|
||||
.details(registrationRequestEsn.toString())
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.channel(getCurrentChannel())
|
||||
.protocol(Protocol.LTR_NET)
|
||||
.build());
|
||||
broadcast(getDecodeEvent(message, DecodeEventType.REQUEST, "REGISTER: " + registrationRequestEsn));
|
||||
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
|
||||
}
|
||||
break;
|
||||
case ISW_REGISTRATION_REQUEST_ESN_LOW:
|
||||
if(message instanceof RegistrationRequestEsnLow)
|
||||
if(message instanceof RegistrationRequestEsnLow registrationRequestEsn)
|
||||
{
|
||||
RegistrationRequestEsnLow registrationRequestEsn = (RegistrationRequestEsnLow)message;
|
||||
|
||||
if(registrationRequestEsn.isCompleteEsn())
|
||||
{
|
||||
getIdentifierCollection().update(registrationRequestEsn.getESN());
|
||||
mESNIdentifiers.add(registrationRequestEsn.getESN());
|
||||
}
|
||||
|
||||
broadcast(DecodeEvent.builder(message.getTimestamp())
|
||||
.eventDescription("Registration Request")
|
||||
.details(registrationRequestEsn.toString())
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.channel(getCurrentChannel())
|
||||
.protocol(Protocol.LTR_NET)
|
||||
.build());
|
||||
|
||||
broadcast(getDecodeEvent(message, DecodeEventType.REQUEST, "REGISTER: " + registrationRequestEsn));
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
|
||||
}
|
||||
break;
|
||||
case ISW_REQUEST_ACCESS:
|
||||
if(message instanceof RequestAccess)
|
||||
if(message instanceof RequestAccess requestAccess)
|
||||
{
|
||||
RequestAccess requestAccess = (RequestAccess)message;
|
||||
|
||||
getIdentifierCollection().update(requestAccess.getTalkgroup());
|
||||
mTalkgroups.add(requestAccess.getTalkgroup());
|
||||
|
||||
broadcast(DecodeEvent.builder(message.getTimestamp())
|
||||
.eventDescription("Access Request")
|
||||
.details(requestAccess.toString())
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.channel(getCurrentChannel())
|
||||
.protocol(Protocol.LTR_NET)
|
||||
.build());
|
||||
|
||||
broadcast(getDecodeEvent(message, DecodeEventType.REQUEST, "ACCESS: " + requestAccess));
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.DATA));
|
||||
}
|
||||
break;
|
||||
case ISW_UNIQUE_ID:
|
||||
if(message instanceof IswUniqueId)
|
||||
if(message instanceof IswUniqueId iswUniqueId)
|
||||
{
|
||||
IswUniqueId iswUniqueId = (IswUniqueId)message;
|
||||
getIdentifierCollection().update(iswUniqueId.getUniqueID());
|
||||
mLtrNetRadioIdentifiers.add(iswUniqueId.getUniqueID());
|
||||
updateCurrentCallIdentifiers();
|
||||
@@ -400,104 +360,90 @@ public class LTRNetDecoderState extends DecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_CALL_END:
|
||||
if(message instanceof OswCallEnd)
|
||||
if(message instanceof OswCallEnd callEnd)
|
||||
{
|
||||
OswCallEnd callEnd = (OswCallEnd)message;
|
||||
mTalkgroups.add(callEnd.getTalkgroup());
|
||||
processCallEnd(callEnd.getChannel(), callEnd.getTalkgroup(), callEnd.getTimestamp());
|
||||
}
|
||||
break;
|
||||
case OSW_CALL_START:
|
||||
if(message instanceof OswCallStart)
|
||||
if(message instanceof OswCallStart callStart)
|
||||
{
|
||||
OswCallStart callStart = (OswCallStart)message;
|
||||
mTalkgroups.add(callStart.getTalkgroup());
|
||||
processCallStart(callStart.getChannel(), callStart.getTalkgroup(), callStart.getTimestamp(),
|
||||
MessageDirection.OSW);
|
||||
}
|
||||
break;
|
||||
case OSW_CHANNEL_MAP_HIGH:
|
||||
if(message instanceof ChannelMapHigh)
|
||||
if(message instanceof ChannelMapHigh channelMapHigh)
|
||||
{
|
||||
mChannelMapHigh = (ChannelMapHigh)message;
|
||||
mChannelMapHigh = channelMapHigh;
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_CHANNEL_MAP_LOW:
|
||||
if(message instanceof ChannelMapLow)
|
||||
if(message instanceof ChannelMapLow channelMapLow)
|
||||
{
|
||||
mChannelMapLow = (ChannelMapLow)message;
|
||||
mChannelMapLow = channelMapLow;
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_SYSTEM_IDLE:
|
||||
if(message instanceof SystemIdle)
|
||||
if(message instanceof SystemIdle systemIdle)
|
||||
{
|
||||
setCurrentChannelNumber(((SystemIdle)message).getChannel());
|
||||
setCurrentChannelNumber(systemIdle.getChannel());
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_NEIGHBOR_ID:
|
||||
if(message instanceof NeighborId)
|
||||
if(message instanceof NeighborId neighborId)
|
||||
{
|
||||
NeighborId neighborId = (NeighborId)message;
|
||||
mNeighborMap.put(neighborId.getNeighborRank(), neighborId);
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_RECEIVE_FREQUENCY_HIGH:
|
||||
if(message instanceof ReceiveFrequencyHigh)
|
||||
if(message instanceof ReceiveFrequencyHigh receiveFrequency)
|
||||
{
|
||||
ReceiveFrequencyHigh receiveFrequency = (ReceiveFrequencyHigh)message;
|
||||
updateReceiveFrequency(receiveFrequency.getChannel(), receiveFrequency.getFrequency());
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_RECEIVE_FREQUENCY_LOW:
|
||||
if(message instanceof ReceiveFrequencyLow)
|
||||
if(message instanceof ReceiveFrequencyLow receiveFrequency)
|
||||
{
|
||||
ReceiveFrequencyLow receiveFrequency = (ReceiveFrequencyLow)message;
|
||||
updateReceiveFrequency(receiveFrequency.getChannel(), receiveFrequency.getFrequency());
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_REGISTRATION_ACCEPT:
|
||||
if(message instanceof RegistrationAccept)
|
||||
if(message instanceof RegistrationAccept registrationAccept)
|
||||
{
|
||||
RegistrationAccept registrationAccept = (RegistrationAccept)message;
|
||||
mLtrNetRadioIdentifiers.add(registrationAccept.getUniqueID());
|
||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
ic.remove(IdentifierClass.USER);
|
||||
ic.update(message.getIdentifiers());
|
||||
|
||||
broadcast(DecodeEvent.builder(message.getTimestamp())
|
||||
.protocol(Protocol.LTR_NET)
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(ic)
|
||||
.eventDescription("Registration Accept")
|
||||
.details(registrationAccept.toString())
|
||||
.build());
|
||||
broadcast(getDecodeEvent(message, DecodeEventType.RESPONSE, "REGISTRATION ACCEPT: " + registrationAccept));
|
||||
}
|
||||
break;
|
||||
case OSW_SITE_ID:
|
||||
if(message instanceof SiteId)
|
||||
if(message instanceof SiteId siteId)
|
||||
{
|
||||
mCurrentSite = (SiteId)message;
|
||||
mCurrentSite = siteId;
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_TRANSMIT_FREQUENCY_HIGH:
|
||||
if(message instanceof TransmitFrequencyHigh)
|
||||
if(message instanceof TransmitFrequencyHigh transmitFrequency)
|
||||
{
|
||||
TransmitFrequencyHigh transmitFrequency = (TransmitFrequencyHigh)message;
|
||||
updateTransmitFrequency(transmitFrequency.getChannel(), transmitFrequency.getFrequency());
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
break;
|
||||
case OSW_TRANSMIT_FREQUENCY_LOW:
|
||||
if(message instanceof TransmitFrequencyLow)
|
||||
if(message instanceof TransmitFrequencyLow transmitFrequency)
|
||||
{
|
||||
TransmitFrequencyLow transmitFrequency = (TransmitFrequencyLow)message;
|
||||
updateTransmitFrequency(transmitFrequency.getChannel(), transmitFrequency.getFrequency());
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.IDLE));
|
||||
@@ -509,6 +455,27 @@ public class LTRNetDecoderState extends DecoderState
|
||||
}
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(IMessage message, DecodeEventType decodeEventType, String details)
|
||||
{
|
||||
return LTRNetDecodeEvent.builder(decodeEventType, message.getTimestamp())
|
||||
.details(details)
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.channel(getCurrentChannel())
|
||||
.build();
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(LTRTalkgroup talkgroup, int channel, long timestamp, DecodeEventType decodeEventType)
|
||||
{
|
||||
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
ic.remove(IdentifierClass.USER);
|
||||
ic.update(talkgroup);
|
||||
|
||||
return LTRNetDecodeEvent.builder(decodeEventType, timestamp)
|
||||
.channel(mChannelMap.get(channel))
|
||||
.identifiers(ic)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
|
||||
@@ -1,52 +1,85 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.ltrnet;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.ltrnet.message.LtrNetMessage;
|
||||
import io.github.dsheirer.module.decode.ltrstandard.LtrStandardMessageType;
|
||||
import java.util.function.Function;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class LTRNetMessageFilter extends Filter<IMessage>
|
||||
/**
|
||||
* Filter for LTR-Net messages
|
||||
*/
|
||||
public class LTRNetMessageFilter extends Filter<IMessage,LtrNetMessageType>
|
||||
{
|
||||
private Map<LtrNetMessageType, FilterElement<LtrNetMessageType>> mElements = new EnumMap<>(LtrNetMessageType.class);
|
||||
private final KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public LTRNetMessageFilter()
|
||||
{
|
||||
super("LTR-Net Message Filter");
|
||||
super("LTR-Net Messages");
|
||||
|
||||
for(LtrNetMessageType type: LtrNetMessageType.values())
|
||||
{
|
||||
mElements.put(type, new FilterElement<>(type));
|
||||
add(new FilterElement<>(type));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IMessage message)
|
||||
{
|
||||
if(mEnabled && canProcess(message))
|
||||
{
|
||||
LtrNetMessage ltr = (LtrNetMessage) message;
|
||||
|
||||
if(mElements.containsKey(ltr.getLtrNetMessageType()))
|
||||
{
|
||||
return mElements.get(ltr.getLtrNetMessageType()).isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for instance of LTR-Net message
|
||||
* @param message to test
|
||||
* @return true if correct type.
|
||||
*/
|
||||
@Override
|
||||
public boolean canProcess(IMessage message)
|
||||
{
|
||||
return message instanceof LtrNetMessage;
|
||||
return message instanceof LtrNetMessage && super.canProcess(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor for LTR-Net messages
|
||||
* @return extractor
|
||||
*/
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
public Function<IMessage, LtrNetMessageType> getKeyExtractor()
|
||||
{
|
||||
return new ArrayList<FilterElement<?>>(mElements.values());
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key extractor
|
||||
*/
|
||||
private class KeyExtractor implements Function<IMessage,LtrNetMessageType>
|
||||
{
|
||||
@Override
|
||||
public LtrNetMessageType apply(IMessage message)
|
||||
{
|
||||
if(message instanceof LtrNetMessage ltr)
|
||||
{
|
||||
return ltr.getLtrNetMessageType();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.ltrnet.message;
|
||||
|
||||
@@ -42,14 +41,25 @@ public abstract class LtrNetMessage extends Message
|
||||
protected CorrectedBinaryMessage mMessage;
|
||||
protected CRC mCRC;
|
||||
private LTRTalkgroup mTalkgroup;
|
||||
private MessageDirection mMessageDirection;
|
||||
|
||||
public LtrNetMessage(CorrectedBinaryMessage message, MessageDirection direction, long timestamp)
|
||||
{
|
||||
super(timestamp);
|
||||
mMessage = message;
|
||||
mMessageDirection = direction;
|
||||
mCRC = CRCLTR.check(message, direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message direction: outbound (OSW) from the repeater or inbound (ISW) to the repeater
|
||||
* @return message direction
|
||||
*/
|
||||
public MessageDirection getMessageDirection()
|
||||
{
|
||||
return mMessageDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Underlying binary message
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user