Compare commits

..

14 Commits

Author SHA1 Message Date
Denny Sheirer
c302c4d121 Merge pull request #1601 from DSheirer/1368-decode-event-filtering-2
1368 Enhance Message & Event Filtering Options
2023-07-14 06:51:57 -04:00
Dennis Sheirer
b0a524cf62 #1368 Restructures Message and Event filter framework. 2023-07-14 06:50:07 -04:00
Christopher Varner
d246ffff30 Adds event type as a filtering option in the Events tab 2023-07-03 05:56:50 -04:00
Denny Sheirer
adfc8b2d6f #1586 Resolves issue in P25 message framer where after transmitting the current message the mDataUnitId is set to null and this causes an NPE when subsequent code is executed prior to shutdown of the channel. (#1593)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-03 05:11:20 -04:00
Denny Sheirer
ad2ab08466 #1588 Use JDK20 and Gradle 8.2. Updates SDRPlay API for compatibility with JDK20 Project Panama Foreign Memory/Function changes. (#1589)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-02 06:42:27 -04:00
Denny Sheirer
f3369410b2 #1590 Resolves issue with RSP tuners generating too many native buffers causing excessive memory consumption (#1591)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-02 06:23:00 -04:00
Denny Sheirer
bac38a5bd8 #1584 Updates DMR & P25 decoders to stop processing residual samples from the current buffer after a traffic channel shutdown has been signalled. (#1585)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-25 03:56:08 -04:00
Denny Sheirer
e5f5df74ad #1563 Updates RSP tuner to produce sample buffers with optimal power-of-2 length to enable downstream vectorized operations. (#1583)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 09:09:30 -04:00
Denny Sheirer
b3837f581a #1581 Updates Rdio Scanner configuration editor to automatically append the 'api/call-upload' path to the end of the protocol and host value provided by the user. Sets the host default value to 'http://localhost' but allows the user to overwrite. (#1582)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 07:05:44 -04:00
Denny Sheirer
b2ab6f58e9 #1579 Resolves issue with array index out of bounds error generated in P25 Phase 1 decoders where the Dibit circular buffer attempts to write a (#1580)
dibit past the length of the array.  Issue likely occurs when a partial/incomplete put operation adds a dibit and increments the buffer pointer but the thread is interrupted prior to wrapping the buffer pointer.  When the next runnable iteration executes, the pointer is pointing past the end of the buffer.  Updates the code to check the pointer both before and after the put operation.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 06:09:57 -04:00
Denny Sheirer
e04b59ba15 #1575 Updates traffic channel names to be 'T-' concatenated with the parent control channel name from the channel configuration. (#1578)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 05:15:27 -04:00
Denny Sheirer
366eacd234 #1573 Adds Digital Coded Squelch (DCS) auxiliary decoder support. (#1574)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-19 05:13:58 -04:00
Denny Sheirer
f792876c0a #1568 Updates NBFM decoder configuration so user can enable/disable high-pass filter option in the audio module to effect audio playback and audio recording of the demodulated audio. Disabling the filter allows recording of DC and sub-audible signalling that may be present in the demodulated audio (e.g. LTR or squelching signalling). (#1569)
Co-authored-by: sheirerd <sheirerd@ainfosec.com>
2023-06-05 07:19:16 -04:00
Denny Sheirer
19f7a5171b #1565 updates LTR, LTRNet and Passport decoders with new slope decoder. Resolves issue with LTR Standard decoder state that was combining individual calls to the same talkgroup and causing excessively long calls. (#1567)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-05 06:13:12 -04:00
330 changed files with 14653 additions and 5987 deletions

View File

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

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

View File

@@ -0,0 +1,230 @@
#ifndef SDRPLAY_API_H
#define SDRPLAY_API_H
#include "sdrplay_api_dev.h"
#include "sdrplay_api_rx_channel.h"
#include "sdrplay_api_callback.h"
//#if defined(_M_X64) || defined(_M_IX86)
//#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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
#
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
# Copyright (C) 2014-2023 Dennis Sheirer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,7 +19,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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -43,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);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,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");
}
}
/**

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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);
}
}

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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]);
}
}
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,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);

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,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()

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,7 +19,6 @@
package io.github.dsheirer.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);

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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
}
}

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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);

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,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;
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,80 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message.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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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;

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,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);

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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;
}
}
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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

View File

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

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,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;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.event;
@@ -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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.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");
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.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;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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);
}

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.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