mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
Compare commits
30 Commits
v0.6.0
...
v0.6.1-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeeed71231 | ||
|
|
f6c4cc8a5f | ||
|
|
d019388034 | ||
|
|
e125d6a452 | ||
|
|
6b5a1fb827 | ||
|
|
0491b08818 | ||
|
|
7b9adc1da2 | ||
|
|
3acaea0019 | ||
|
|
be02393f65 | ||
|
|
614bf72f01 | ||
|
|
bed28b82ab | ||
|
|
c047d48d91 | ||
|
|
5222bffce4 | ||
|
|
42e7a49d37 | ||
|
|
686bddf7d5 | ||
|
|
1c420b8eee | ||
|
|
e2a0325d07 | ||
|
|
9d03c151af | ||
|
|
3c18bfdc81 | ||
|
|
d89ba65238 | ||
|
|
d2cf2ee7b5 | ||
|
|
7a03331955 | ||
|
|
7c5ef3803a | ||
|
|
6f3e7c86e5 | ||
|
|
fc51de2b4f | ||
|
|
e1e75710cb | ||
|
|
87ce08e9b9 | ||
|
|
1a5c7521a5 | ||
|
|
b54e94dfbe | ||
|
|
19977d5f4d |
@@ -30,7 +30,11 @@ typedef void *HANDLE;
|
||||
..\SDRtrunk\sdrplay-api\src\main\java
|
||||
|
||||
4. Generate the code:
|
||||
c:\Users\Denny\Downloads\jdk-19-ea2-panama\bin\jextract "C:\Users\Denny\git\sdrtrunk\sdrplay-api\api\v3_11\sdrplay_api.h" -l libsdrplay_api --source
|
||||
Windows:
|
||||
c:\Users\Denny\Downloads\jdk-19-ea2-panama\bin\jextract "C:\Users\Denny\git\sdrtrunk\sdrplay-api\api\v3_11\sdrplay_api.h" -l libsdrplay_api --source -t io.github.dsheirer.source.tuner.sdrplay.api.v3_14
|
||||
|
||||
5. Do this for all versions of the API.
|
||||
Linux:
|
||||
~/Downloads/jextract-20/bin/jextract ~/IdeaProjects/sdrtrunk/artifacts/sdrplay-api-headers/v3_14/sdrplay_api.h -l libsdrplay_api --source -t io.github.dsheirer.source.tuner.sdrplay.api.v3_14
|
||||
|
||||
5. Compare the 3.07 header files to the newest version header files. Delete any auto-generated files that have not
|
||||
changed and then update the factory class to construct variation classes where there are differences.
|
||||
|
||||
234
artifacts/sdrplay-api-headers/v3_14/sdrplay_api.h
Normal file
234
artifacts/sdrplay-api-headers/v3_14/sdrplay_api.h
Normal file
@@ -0,0 +1,234 @@
|
||||
#ifndef SDRPLAY_API_H
|
||||
#define SDRPLAY_API_H
|
||||
|
||||
#include "sdrplay_api_dev.h"
|
||||
#include "sdrplay_api_rx_channel.h"
|
||||
#include "sdrplay_api_callback.h"
|
||||
|
||||
#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
|
||||
#include "windows.h"
|
||||
#elif defined (__GNUC__)
|
||||
typedef void *HANDLE;
|
||||
#endif
|
||||
|
||||
#ifndef _SDRPLAY_DLL_QUALIFIER
|
||||
#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
|
||||
#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
|
||||
#elif defined(STATIC_LIB) || defined(__GNUC__)
|
||||
#define _SDRPLAY_DLL_QUALIFIER
|
||||
#endif
|
||||
#endif // _SDRPLAY_DLL_QUALIFIER
|
||||
|
||||
// Application code should check that it is compiled against the same API version
|
||||
// sdrplay_api_ApiVersion() returns the API version
|
||||
#define SDRPLAY_API_VERSION (float)(3.14)
|
||||
|
||||
// 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)
|
||||
#define SDRPLAY_RSP1B_ID (6)
|
||||
|
||||
// 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,
|
||||
sdrplay_api_Update_RspDuo_ResetSlaveFlags = 0x00000040,
|
||||
|
||||
// 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_ErrorInfoT* (*sdrplay_api_GetLastErrorByType_t)(sdrplay_api_DeviceT *device, int type, unsigned long long *time);
|
||||
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_ErrorInfoT* sdrplay_api_GetLastErrorByType(sdrplay_api_DeviceT *device, int type, unsigned long long *time);
|
||||
_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
|
||||
79
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_callback.h
Normal file
79
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_callback.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#ifndef SDRPLAY_API_CALLBACK_H
|
||||
#define SDRPLAY_API_CALLBACK_H
|
||||
|
||||
// Event callback enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Overload_Detected = 0,
|
||||
sdrplay_api_Overload_Corrected = 1,
|
||||
} sdrplay_api_PowerOverloadCbEventIdT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_MasterInitialised = 0,
|
||||
sdrplay_api_SlaveAttached = 1,
|
||||
sdrplay_api_SlaveDetached = 2,
|
||||
sdrplay_api_SlaveInitialised = 3,
|
||||
sdrplay_api_SlaveUninitialised = 4,
|
||||
sdrplay_api_MasterDllDisappeared = 5,
|
||||
sdrplay_api_SlaveDllDisappeared = 6,
|
||||
} sdrplay_api_RspDuoModeCbEventIdT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_GainChange = 0,
|
||||
sdrplay_api_PowerOverloadChange = 1,
|
||||
sdrplay_api_DeviceRemoved = 2,
|
||||
sdrplay_api_RspDuoModeChange = 3,
|
||||
sdrplay_api_DeviceFailure = 4,
|
||||
} sdrplay_api_EventT;
|
||||
|
||||
// Event callback parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned int gRdB;
|
||||
unsigned int lnaGRdB;
|
||||
double currGain;
|
||||
} sdrplay_api_GainCbParamT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
|
||||
} sdrplay_api_PowerOverloadCbParamT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
|
||||
} sdrplay_api_RspDuoModeCbParamT;
|
||||
|
||||
// Event parameters overlay
|
||||
typedef union
|
||||
{
|
||||
sdrplay_api_GainCbParamT gainParams;
|
||||
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
|
||||
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
|
||||
} sdrplay_api_EventParamsT;
|
||||
|
||||
// Stream callback parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned int firstSampleNum;
|
||||
int grChanged;
|
||||
int rfChanged;
|
||||
int fsChanged;
|
||||
unsigned int numSamples;
|
||||
} sdrplay_api_StreamCbParamsT;
|
||||
|
||||
// Callback function prototypes
|
||||
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
|
||||
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
|
||||
|
||||
// Callback function struct
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_StreamCallback_t StreamACbFn;
|
||||
sdrplay_api_StreamCallback_t StreamBCbFn;
|
||||
sdrplay_api_EventCallback_t EventCbFn;
|
||||
} sdrplay_api_CallbackFnsT;
|
||||
|
||||
#endif //SDRPLAY_API_CALLBACK_H
|
||||
55
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_control.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef SDRPLAY_API_CONTROL_H
|
||||
#define SDRPLAY_API_CONTROL_H
|
||||
|
||||
// Control parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_AGC_DISABLE = 0,
|
||||
sdrplay_api_AGC_100HZ = 1,
|
||||
sdrplay_api_AGC_50HZ = 2,
|
||||
sdrplay_api_AGC_5HZ = 3,
|
||||
sdrplay_api_AGC_CTRL_EN = 4
|
||||
} sdrplay_api_AgcControlT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_ADSB_DECIMATION = 0,
|
||||
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
|
||||
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
|
||||
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
|
||||
} sdrplay_api_AdsbModeT;
|
||||
|
||||
// Control parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char DCenable; // default: 1
|
||||
unsigned char IQenable; // default: 1
|
||||
} sdrplay_api_DcOffsetT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char enable; // default: 0
|
||||
unsigned char decimationFactor; // default: 1
|
||||
unsigned char wideBandSignal; // default: 0
|
||||
} sdrplay_api_DecimationT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
|
||||
int setPoint_dBfs; // default: -60
|
||||
unsigned short attack_ms; // default: 0
|
||||
unsigned short decay_ms; // default: 0
|
||||
unsigned short decay_delay_ms; // default: 0
|
||||
unsigned short decay_threshold_dB; // default: 0
|
||||
int syncUpdate; // default: 0
|
||||
} sdrplay_api_AgcT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_DcOffsetT dcOffset;
|
||||
sdrplay_api_DecimationT decimation;
|
||||
sdrplay_api_AgcT agc;
|
||||
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
|
||||
} sdrplay_api_ControlParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_CONTROL_H
|
||||
51
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_dev.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef SDRPLAY_API_DEV_H
|
||||
#define SDRPLAY_API_DEV_H
|
||||
|
||||
#include "sdrplay_api_rsp1a.h"
|
||||
#include "sdrplay_api_rsp2.h"
|
||||
#include "sdrplay_api_rspDuo.h"
|
||||
#include "sdrplay_api_rspDx.h"
|
||||
|
||||
// Dev parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_ISOCH = 0,
|
||||
sdrplay_api_BULK = 1
|
||||
} sdrplay_api_TransferModeT;
|
||||
|
||||
// Dev parameter structs
|
||||
typedef struct
|
||||
{
|
||||
double fsHz; // default: 2000000.0
|
||||
unsigned char syncUpdate; // default: 0
|
||||
unsigned char reCal; // default: 0
|
||||
} sdrplay_api_FsFreqT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int sampleNum; // default: 0
|
||||
unsigned int period; // default: 0
|
||||
} sdrplay_api_SyncUpdateT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char resetGainUpdate; // default: 0
|
||||
unsigned char resetRfUpdate; // default: 0
|
||||
unsigned char resetFsUpdate; // default: 0
|
||||
} sdrplay_api_ResetFlagsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double ppm; // default: 0.0
|
||||
sdrplay_api_FsFreqT fsFreq;
|
||||
sdrplay_api_SyncUpdateT syncUpdate;
|
||||
sdrplay_api_ResetFlagsT resetFlags;
|
||||
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
|
||||
unsigned int samplesPerPkt; // default: 0 (output param)
|
||||
sdrplay_api_Rsp1aParamsT rsp1aParams;
|
||||
sdrplay_api_Rsp2ParamsT rsp2Params;
|
||||
sdrplay_api_RspDuoParamsT rspDuoParams;
|
||||
sdrplay_api_RspDxParamsT rspDxParams;
|
||||
} sdrplay_api_DevParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_DEV_H
|
||||
22
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rsp1a.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef SDRPLAY_API_RSP1A_H
|
||||
#define SDRPLAY_API_RSP1A_H
|
||||
|
||||
#define RSPIA_NUM_LNA_STATES 10
|
||||
#define RSPIA_NUM_LNA_STATES_AM 7
|
||||
#define RSPIA_NUM_LNA_STATES_LBAND 9
|
||||
|
||||
// RSP1A parameter enums
|
||||
|
||||
// RSP1A parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char rfNotchEnable; // default: 0
|
||||
unsigned char rfDabNotchEnable; // default: 0
|
||||
} sdrplay_api_Rsp1aParamsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char biasTEnable; // default: 0
|
||||
} sdrplay_api_Rsp1aTunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSP1A_H
|
||||
35
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rsp2.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef SDRPLAY_API_RSP2_H
|
||||
#define SDRPLAY_API_RSP2_H
|
||||
|
||||
#define RSPII_NUM_LNA_STATES 9
|
||||
#define RSPII_NUM_LNA_STATES_AMPORT 5
|
||||
#define RSPII_NUM_LNA_STATES_420MHZ 6
|
||||
|
||||
// RSP2 parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Rsp2_ANTENNA_A = 5,
|
||||
sdrplay_api_Rsp2_ANTENNA_B = 6,
|
||||
} sdrplay_api_Rsp2_AntennaSelectT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Rsp2_AMPORT_1 = 1,
|
||||
sdrplay_api_Rsp2_AMPORT_2 = 0,
|
||||
} sdrplay_api_Rsp2_AmPortSelectT;
|
||||
|
||||
// RSP2 parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char extRefOutputEn; // default: 0
|
||||
} sdrplay_api_Rsp2ParamsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char biasTEnable; // default: 0
|
||||
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
|
||||
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
|
||||
unsigned char rfNotchEnable; // default: 0
|
||||
} sdrplay_api_Rsp2TunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSP2_H
|
||||
47
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rspDuo.h
Normal file
47
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rspDuo.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#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 resetGainUpdate; // default: 0
|
||||
unsigned char resetRfUpdate; // default: 0
|
||||
} sdrplay_api_RspDuo_ResetSlaveFlagsT;
|
||||
|
||||
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_RspDuo_ResetSlaveFlagsT resetSlaveFlags;
|
||||
} sdrplay_api_RspDuoTunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSPduo_H
|
||||
46
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rspDx.h
Normal file
46
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rspDx.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef SDRPLAY_API_RSPDX_H
|
||||
#define SDRPLAY_API_RSPDX_H
|
||||
|
||||
#include "sdrplay_api_tuner.h"
|
||||
|
||||
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
|
||||
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
|
||||
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_50 20 // Number of LNA states when using AM Port 2 between 12 and 50MHz
|
||||
#define RSPDX_NUM_LNA_STATES_AMPORT2_50_60 25 // Number of LNA states when using AM Port 2 between 50 and 60MHz
|
||||
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
|
||||
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
|
||||
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
|
||||
#define RSPDX_NUM_LNA_STATES_DX 22 // Number of LNA states in DX path
|
||||
|
||||
// RSPdx parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_RspDx_ANTENNA_A = 0,
|
||||
sdrplay_api_RspDx_ANTENNA_B = 1,
|
||||
sdrplay_api_RspDx_ANTENNA_C = 2,
|
||||
} sdrplay_api_RspDx_AntennaSelectT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
|
||||
} sdrplay_api_RspDx_HdrModeBwT;
|
||||
|
||||
// RSPdx parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char hdrEnable; // default: 0
|
||||
unsigned char biasTEnable; // default: 0
|
||||
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
|
||||
unsigned char rfNotchEnable; // default: 0
|
||||
unsigned char rfDabNotchEnable; // default: 0
|
||||
} sdrplay_api_RspDxParamsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
|
||||
} sdrplay_api_RspDxTunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSPDX_H
|
||||
21
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_rx_channel.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef SDRPLAY_API_RX_CHANNEL_H
|
||||
#define SDRPLAY_API_RX_CHANNEL_H
|
||||
|
||||
#include "sdrplay_api_tuner.h"
|
||||
#include "sdrplay_api_control.h"
|
||||
#include "sdrplay_api_rsp1a.h"
|
||||
#include "sdrplay_api_rsp2.h"
|
||||
#include "sdrplay_api_rspDuo.h"
|
||||
#include "sdrplay_api_rspDx.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_TunerParamsT tunerParams;
|
||||
sdrplay_api_ControlParamsT ctrlParams;
|
||||
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
|
||||
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
|
||||
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
|
||||
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
|
||||
} sdrplay_api_RxChannelParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RX_CHANNEL_H
|
||||
93
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_14/sdrplay_api_tuner.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef SDRPLAY_API_TUNER_H
|
||||
#define SDRPLAY_API_TUNER_H
|
||||
|
||||
#define MAX_BB_GR (59)
|
||||
|
||||
// Tuner parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_BW_Undefined = 0,
|
||||
sdrplay_api_BW_0_200 = 200,
|
||||
sdrplay_api_BW_0_300 = 300,
|
||||
sdrplay_api_BW_0_600 = 600,
|
||||
sdrplay_api_BW_1_536 = 1536,
|
||||
sdrplay_api_BW_5_000 = 5000,
|
||||
sdrplay_api_BW_6_000 = 6000,
|
||||
sdrplay_api_BW_7_000 = 7000,
|
||||
sdrplay_api_BW_8_000 = 8000
|
||||
} sdrplay_api_Bw_MHzT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_IF_Undefined = -1,
|
||||
sdrplay_api_IF_Zero = 0,
|
||||
sdrplay_api_IF_0_450 = 450,
|
||||
sdrplay_api_IF_1_620 = 1620,
|
||||
sdrplay_api_IF_2_048 = 2048
|
||||
} sdrplay_api_If_kHzT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_LO_Undefined = 0,
|
||||
sdrplay_api_LO_Auto = 1,
|
||||
sdrplay_api_LO_120MHz = 2,
|
||||
sdrplay_api_LO_144MHz = 3,
|
||||
sdrplay_api_LO_168MHz = 4
|
||||
} sdrplay_api_LoModeT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_EXTENDED_MIN_GR = 0,
|
||||
sdrplay_api_NORMAL_MIN_GR = 20
|
||||
} sdrplay_api_MinGainReductionT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Tuner_Neither = 0,
|
||||
sdrplay_api_Tuner_A = 1,
|
||||
sdrplay_api_Tuner_B = 2,
|
||||
sdrplay_api_Tuner_Both = 3,
|
||||
} sdrplay_api_TunerSelectT;
|
||||
|
||||
// Tuner parameter structs
|
||||
typedef struct
|
||||
{
|
||||
float curr;
|
||||
float max;
|
||||
float min;
|
||||
} sdrplay_api_GainValuesT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int gRdB; // default: 50
|
||||
unsigned char LNAstate; // default: 0
|
||||
unsigned char syncUpdate; // default: 0
|
||||
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
|
||||
sdrplay_api_GainValuesT gainVals; // output parameter
|
||||
} sdrplay_api_GainT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double rfHz; // default: 200000000.0
|
||||
unsigned char syncUpdate; // default: 0
|
||||
} sdrplay_api_RfFreqT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char dcCal; // default: 3 (Periodic mode)
|
||||
unsigned char speedUp; // default: 0 (No speedup)
|
||||
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
|
||||
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
|
||||
} sdrplay_api_DcOffsetTunerT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
|
||||
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
|
||||
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
|
||||
sdrplay_api_GainT gain;
|
||||
sdrplay_api_RfFreqT rfFreq;
|
||||
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
|
||||
} sdrplay_api_TunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_TUNER_H
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# ******************************************************************************
|
||||
# Copyright (C) 2014-2023 Dennis Sheirer
|
||||
# Copyright (C) 2014-2024 Dennis Sheirer
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -28,4 +28,4 @@
|
||||
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
||||
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
||||
|
||||
projectVersion=0.6.0
|
||||
projectVersion=0.6.1-beta-1
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,12 +28,9 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
|
||||
import io.github.dsheirer.preference.duplicate.CallManagementPreference;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -42,6 +39,8 @@ import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Detects duplicate calls that occur within the same system. This detector is thread safe for the receive() method.
|
||||
@@ -52,18 +51,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetector.class);
|
||||
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
|
||||
private CallManagementPreference mCallManagementPreference;
|
||||
private Map<String,SystemDuplicateCallDetector> mDetectorMap = new HashMap();
|
||||
|
||||
public DuplicateCallDetector(UserPreferences userPreferences)
|
||||
{
|
||||
mDuplicateCallDetectionPreference = userPreferences.getDuplicateCallDetectionPreference();
|
||||
mCallManagementPreference = userPreferences.getDuplicateCallDetectionPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(AudioSegment audioSegment)
|
||||
{
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionEnabled())
|
||||
if(mCallManagementPreference.isDuplicateCallDetectionEnabled())
|
||||
{
|
||||
Identifier identifier = audioSegment.getIdentifierCollection()
|
||||
.getIdentifier(IdentifierClass.CONFIGURATION, Form.SYSTEM, Role.ANY);
|
||||
@@ -137,7 +136,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||
*/
|
||||
private boolean isDuplicate(AudioSegment segment1, AudioSegment segment2)
|
||||
{
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByTalkgroupEnabled())
|
||||
if(mCallManagementPreference.isDuplicateCallDetectionByTalkgroupEnabled())
|
||||
{
|
||||
//Step 1 check for duplicate TO values
|
||||
List<Identifier> to1 = segment1.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||
@@ -149,7 +148,7 @@ public class DuplicateCallDetector implements Listener<AudioSegment>
|
||||
}
|
||||
}
|
||||
|
||||
if(mDuplicateCallDetectionPreference.isDuplicateCallDetectionByRadioEnabled())
|
||||
if(mCallManagementPreference.isDuplicateCallDetectionByRadioEnabled())
|
||||
{
|
||||
//Step 2 check for duplicate FROM values
|
||||
List<Identifier> from1 = segment1.getIdentifierCollection().getIdentifiers(Role.FROM);
|
||||
|
||||
@@ -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,25 +19,34 @@
|
||||
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||
import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||
import io.github.dsheirer.record.RecordFormat;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import io.github.dsheirer.util.TimeStamp;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Audio streaming manager monitors audio segments through completion and creates temporary streaming recordings on
|
||||
@@ -138,29 +147,60 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
||||
|
||||
if(mAudioRecordingListener != null && audioSegment.hasBroadcastChannels())
|
||||
{
|
||||
Path path = getTemporaryRecordingPath();
|
||||
long length = 0;
|
||||
|
||||
for(float[] audioBuffer: audioSegment.getAudioBuffers())
|
||||
{
|
||||
length += audioBuffer.length;
|
||||
}
|
||||
|
||||
length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond.
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences);
|
||||
IdentifierCollection identifierCollectionCopy =
|
||||
IdentifierCollection identifiers =
|
||||
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||
|
||||
AudioRecording audioRecording = new AudioRecording(path, audioSegment.getBroadcastChannels(),
|
||||
identifierCollectionCopy, audioSegment.getStartTimestamp(), length);
|
||||
mAudioRecordingListener.receive(audioRecording);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
if(identifiers.getToIdentifier() instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
mLog.error("Error recording temporary stream MP3");
|
||||
if(mUserPreferences.getDuplicateCallDetectionPreference()
|
||||
.getPatchGroupStreamingOption() == PatchGroupStreamingOption.TALKGROUPS)
|
||||
{
|
||||
//Decompose the patch group into the individual (patched) talkgroups and process the audio
|
||||
//segment for each patched talkgroup.
|
||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||
|
||||
List<Identifier> ids = new ArrayList<>();
|
||||
ids.addAll(patchGroup.getPatchedTalkgroupIdentifiers());
|
||||
ids.addAll(patchGroup.getPatchedRadioIdentifiers());
|
||||
|
||||
//If there are no patched radios/talkgroups, override user preference and stream as a patch group
|
||||
if(ids.isEmpty() || audioSegment.getAliasList() == null)
|
||||
{
|
||||
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
|
||||
}
|
||||
else
|
||||
{
|
||||
AliasList aliasList = audioSegment.getAliasList();
|
||||
|
||||
for(Identifier identifier: ids)
|
||||
{
|
||||
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||
Set<BroadcastChannel> broadcastChannels = new HashSet<>();
|
||||
for(Alias alias: aliases)
|
||||
{
|
||||
broadcastChannels.addAll(alias.getBroadcastChannels());
|
||||
}
|
||||
|
||||
if(!broadcastChannels.isEmpty())
|
||||
{
|
||||
MutableIdentifierCollection decomposedIdentifiers =
|
||||
new MutableIdentifierCollection(identifiers.getIdentifiers());
|
||||
//Remove patch group TO identifier & replace with the patched talkgroup/radio
|
||||
decomposedIdentifiers.remove(Role.TO);
|
||||
decomposedIdentifiers.update(identifier);
|
||||
processAudioSegment(audioSegment, decomposedIdentifiers, broadcastChannels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
processAudioSegment(audioSegment, identifiers, audioSegment.getBroadcastChannels());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +209,40 @@ public class AudioStreamingManager implements Listener<AudioSegment>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an audio segment for streaming by creating a temporary MP3 recording and submitting the recording
|
||||
* to the specific broadcast channel(s).
|
||||
* @param audioSegment to process for streaming
|
||||
* @param identifierCollection to use for the streamed audio recording.
|
||||
* @param broadcastChannels to receive the audio recording
|
||||
*/
|
||||
private void processAudioSegment(AudioSegment audioSegment, IdentifierCollection identifierCollection,
|
||||
Set<BroadcastChannel> broadcastChannels)
|
||||
{
|
||||
Path path = getTemporaryRecordingPath();
|
||||
long length = 0;
|
||||
|
||||
for(float[] audioBuffer: audioSegment.getAudioBuffers())
|
||||
{
|
||||
length += audioBuffer.length;
|
||||
}
|
||||
|
||||
length /= 8; //Sample rate is 8000 samples per second, or 8 samples per millisecond.
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences, identifierCollection);
|
||||
|
||||
AudioRecording audioRecording = new AudioRecording(path, broadcastChannels, identifierCollection,
|
||||
audioSegment.getStartTimestamp(), length);
|
||||
mAudioRecordingListener.receive(audioRecording);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error recording temporary stream MP3");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary streaming recording file path
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
/**
|
||||
* Options for streaming management of patch groups. These options are used when streaming an audio call that is
|
||||
* tagged to a patch group. The audio will either be streamed once and identified as the patch group, or it will
|
||||
* be streamed multiple times, once for each individual patched talkgroup.
|
||||
*/
|
||||
public enum PatchGroupStreamingOption
|
||||
{
|
||||
PATCH_GROUP("Patch Group"),
|
||||
TALKGROUPS("Individual Talkgroups");
|
||||
|
||||
private String mLabel;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param label to display
|
||||
*/
|
||||
PatchGroupStreamingOption(String label)
|
||||
{
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return mLabel;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.ConnectException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
@@ -69,6 +70,7 @@
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Audio broadcaster to push completed audio recordings to the OpenMHz call upload API.
|
||||
*
|
||||
@@ -115,14 +117,15 @@
|
||||
String response = testConnection(getBroadcastConfiguration());
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* OpenMHz API does not currently expose a test method.
|
||||
* TODO: FIX THIS
|
||||
*/
|
||||
if(response == "OK")// && response.toLowerCase().startsWith("<head><title>502 bad gateway</title></head>"))
|
||||
if(response == "OK")
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
}
|
||||
else if (response == "No Response")
|
||||
{
|
||||
setBroadcastState(BroadcastState.NO_SERVER);
|
||||
mLog.error("Error connecting to OpenMHz server [Server not found or not reachable]");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error connecting to OpenMHz server on startup [" + response + "]");
|
||||
@@ -170,8 +173,6 @@
|
||||
* Indicates if this broadcaster continues to have successful connections to and transactions with the remote
|
||||
* server. If there is a connectivity or other issue, the broadcast state is set to temporary error and
|
||||
* the audio processor thread will persistently invoke this method to attempt a reconnect.
|
||||
*
|
||||
* OpenMHz does not have a test API endpoint, so we look for the incomplete call response.
|
||||
*/
|
||||
private boolean connected()
|
||||
{
|
||||
@@ -183,14 +184,21 @@
|
||||
String response = testConnection(getBroadcastConfiguration());
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
if(response != null && response == "200")
|
||||
if(response == "OK")
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
}
|
||||
else if (response == "No Response")
|
||||
{
|
||||
setBroadcastState(BroadcastState.NO_SERVER);
|
||||
mLog.error("Error reconnecting to OpenMHz server [Server not found or not reachable]");
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
mLog.error("Error reconnecting to OpenMHz server [" + response + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return getBroadcastState() == BroadcastState.CONNECTED;
|
||||
@@ -565,8 +573,8 @@
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponse<String> response = httpClient.send(request, responseHandler);
|
||||
String responseBody = response.body();
|
||||
HttpResponse<String> response = httpClient.send(request, responseHandler);
|
||||
|
||||
if (response.statusCode() == 200)
|
||||
{
|
||||
return "OK";
|
||||
@@ -583,8 +591,16 @@
|
||||
return "No Response";
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return e.getLocalizedMessage();
|
||||
{
|
||||
Throwable throwableCause = e.getCause();
|
||||
|
||||
if(throwableCause instanceof ConnectException)
|
||||
{
|
||||
return "No Response";
|
||||
}
|
||||
|
||||
mLog.error("Exception connecting to OpenMHz server [" + e.toString() + "]");
|
||||
return "Uknown Exception";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ public class MBECallSequenceConverter
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.recordWAVE(audioSegment, outputPath);
|
||||
AudioSegmentRecorder.recordWAVE(audioSegment, outputPath, audioSegment.getIdentifierCollection());
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
|
||||
@@ -307,8 +307,8 @@ public class BinaryMessage extends BitSet
|
||||
|
||||
/**
|
||||
* Overrides the parent method which returns a BitSet so that we can return a BinaryMessage
|
||||
* @param from
|
||||
* @param to
|
||||
* @param from inclusive
|
||||
* @param to exclusive
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@@ -1198,9 +1198,22 @@ public class BinaryMessage extends BitSet
|
||||
{
|
||||
byte[] bytes = new byte[characterCount];
|
||||
|
||||
int index;
|
||||
for(int x = 0; x < characterCount; x++)
|
||||
{
|
||||
bytes[x] = getByte(CHARACTER_7_BIT, x * 7 + offset);
|
||||
index = x * 7 + offset;
|
||||
|
||||
if(index == 0)
|
||||
{
|
||||
bytes[x] = getByte(CHARACTER_8_BIT, index);
|
||||
bytes[x] = (byte)((bytes[x] >> 1) & 0x7F);
|
||||
}
|
||||
else
|
||||
{
|
||||
index--;
|
||||
bytes[x] = getByte(CHARACTER_8_BIT, index);
|
||||
bytes[x] &= (byte)0x7F; //mask off the high bit since we have to parse it as an 8-bit byte.
|
||||
}
|
||||
}
|
||||
|
||||
return new String(bytes);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -35,11 +35,10 @@ import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier
|
||||
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
||||
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Channel metadata containing details about the channel configuration, decoder state and current
|
||||
@@ -66,17 +65,51 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
|
||||
private AliasModel mAliasModel;
|
||||
private AliasList mAliasList;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param aliasModel for alias lookups
|
||||
* @param timeslot for this metadata
|
||||
*/
|
||||
public ChannelMetadata(AliasModel aliasModel, Integer timeslot)
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mTimeslot = timeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param aliasModel for alias lookups
|
||||
*/
|
||||
public ChannelMetadata(AliasModel aliasModel)
|
||||
{
|
||||
this(aliasModel, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a textual description of this channel metadata.
|
||||
*/
|
||||
public String getDescription()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Channel Metadata Description\n");
|
||||
sb.append("\tTimeslot: ").append(mTimeslot).append("\n");
|
||||
Identifier decoder = getDecoderTypeConfigurationIdentifier();
|
||||
sb.append("\tDecoder: ").append(decoder != null ? decoder : "(null)").append("\n");
|
||||
Identifier state = getChannelStateIdentifier();
|
||||
sb.append("\tState: ").append(state != null ? state : "(null)").append("\n");
|
||||
Identifier system = getSystemConfigurationIdentifier();
|
||||
sb.append("\tSystem: ").append(system != null ? system : "(null)").append("\n");
|
||||
Identifier site = getSiteConfigurationIdentifier();
|
||||
sb.append("\tSite: ").append(site != null ? site : "(null)").append("\n");
|
||||
Identifier channel = getChannelNameConfigurationIdentifier();
|
||||
sb.append("\tChannel: ").append(channel != null ? channel : "(null)").append("\n");
|
||||
Identifier frequency = getFrequencyConfigurationIdentifier();
|
||||
sb.append("\tFrequency: ").append(frequency != null ? frequency : "(null)").append("\n");
|
||||
Identifier logical = getDecoderLogicalChannelNameIdentifier();
|
||||
sb.append("\tLogical Channel: ").append(logical != null ? logical : "(null)").append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Integer getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.channel.metadata;
|
||||
|
||||
@@ -29,16 +26,16 @@ import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.awt.EventQueue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
public class ChannelMetadataModel extends AbstractTableModel implements IChannelMetadataUpdateListener
|
||||
{
|
||||
@@ -84,6 +81,47 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a diagnostic description of the channel metadata and metadata-channel map.
|
||||
* @return diagnostic description.
|
||||
*/
|
||||
public String getDiagnosticInformation()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Map<ChannelMetadata,Channel> mapCopy = new HashMap<>(mMetadataChannelMap);
|
||||
List<ChannelMetadata> metadataCopy = new ArrayList<>(mChannelMetadata);
|
||||
|
||||
sb.append("Channel Metadata Model Diagnostics\n");
|
||||
|
||||
for(ChannelMetadata channelMetadata: metadataCopy)
|
||||
{
|
||||
sb.append("\n--------------- CHANNEL METADATA ITEM --------------------\n\n");
|
||||
if(mapCopy.containsKey(channelMetadata))
|
||||
{
|
||||
Channel channel = mapCopy.get(channelMetadata);
|
||||
sb.append("\tMapped Channel:").append(channel).append("\n");
|
||||
sb.append("\t\tSource Configuration: ").append(channel.getSourceConfiguration()).append("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("\tMapped Channel: **Channel Metadata Is Not In the Metadata:Channel Map**\n");
|
||||
}
|
||||
|
||||
sb.append(channelMetadata.getDescription());
|
||||
}
|
||||
|
||||
//Check for orphaned map entries that don't match the current channel metadata list.
|
||||
for(Map.Entry<ChannelMetadata,Channel> entry: mapCopy.entrySet())
|
||||
{
|
||||
if(!metadataCopy.contains(entry.getKey()))
|
||||
{
|
||||
sb.append("\n\tWARNING: orphaned Metadata Channel map entry - channel: " + entry.getValue()).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
@@ -241,7 +279,8 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
|
||||
}
|
||||
else
|
||||
{
|
||||
String value = id.getValue().replace(" TS0", "").replace(" TS1", "");
|
||||
String value = id.getValue().replace(" TS1", "")
|
||||
.replace(" TS2", "");
|
||||
return value + " TS:" + channelMetadata.getTimeslot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 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.channel.state;
|
||||
@@ -47,9 +44,37 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
|
||||
protected Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
|
||||
protected Listener<DecoderStateEvent> mDecoderStateListener;
|
||||
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
|
||||
private boolean mRunning;
|
||||
|
||||
public abstract DecoderType getDecoderType();
|
||||
|
||||
/**
|
||||
* Implements module start and sets the mRunning flag to true so that messages can be processed.
|
||||
*/
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
mRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the module stop and sets the mRunning flag to false to stop message processing
|
||||
*/
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this module is running and can process/pass messages down to sub-class implementations.
|
||||
* @return true if running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides subclass reference to the decode event broadcaster
|
||||
*/
|
||||
@@ -145,4 +170,22 @@ public abstract class AbstractDecoderState extends Module implements ActivitySum
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Message listener that only passes messages while we're running. This is important because each of the decoders
|
||||
* can process blocks of samples and that can result in additional messages being generated even after shutdown
|
||||
* and so we shut off the processing of decoded messages when we're commanded to stop. The sample processing thread
|
||||
* cannot be shutdown or forcefully interrupted because downstream decoder and channel states may have acquired
|
||||
* locks that have to be properly released.
|
||||
*/
|
||||
private class MessageListener implements Listener<IMessage>
|
||||
{
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
if(isRunning())
|
||||
{
|
||||
receive(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
|
||||
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.identifier.string.SimpleStringIdentifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Basic decoder channel state - provides the minimum channel state functionality
|
||||
* to support an always un-squelched audio decoder.
|
||||
*/
|
||||
public class AlwaysUnsquelchedDecoderState extends DecoderState
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AlwaysUnsquelchedDecoderState.class);
|
||||
private static final String NO_SQUELCH = "No Squelch";
|
||||
private Identifier mChannelNameIdentifier;
|
||||
|
||||
private DecoderType mDecoderType;
|
||||
private String mChannelName;
|
||||
|
||||
public AlwaysUnsquelchedDecoderState(DecoderType decoderType, String channelName)
|
||||
{
|
||||
mDecoderType = decoderType;
|
||||
mChannelName = (channelName != null && !channelName.isEmpty()) ? channelName : decoderType.name() + " CHANNEL";
|
||||
mChannelNameIdentifier = new SimpleStringIdentifier(mChannelName, IdentifierClass.USER, Form.CHANNEL_NAME, Role.TO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Activity Summary\n");
|
||||
sb.append("\tDecoder:\t");
|
||||
sb.append(mDecoderType);
|
||||
sb.append("\n\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(IMessage t)
|
||||
{
|
||||
/* Not implemented */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveDecoderStateEvent(DecoderStateEvent event)
|
||||
{
|
||||
if(event.getEvent() == Event.REQUEST_RESET)
|
||||
{
|
||||
getIdentifierCollection().update(mChannelNameIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return mDecoderType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.REQUEST_ALWAYS_UNSQUELCH, State.IDLE));
|
||||
getIdentifierCollection().update(mChannelNameIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -54,6 +54,7 @@ public abstract class DecoderState extends AbstractDecoderState
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
//Broadcast the existing identifiers (as add events) so that they can be received by external listeners
|
||||
mIdentifierCollection.broadcastIdentifiers();
|
||||
}
|
||||
@@ -95,7 +96,7 @@ public abstract class DecoderState extends AbstractDecoderState
|
||||
/**
|
||||
* Sets the current channel descriptor
|
||||
*/
|
||||
protected void setCurrentChannel(IChannelDescriptor channel)
|
||||
public void setCurrentChannel(IChannelDescriptor channel)
|
||||
{
|
||||
mCurrentChannel = channel;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -24,6 +24,7 @@ import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.channel.metadata.ChannelAndMetadata;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadataModel;
|
||||
import io.github.dsheirer.channel.state.AbstractChannelState;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
|
||||
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
||||
import io.github.dsheirer.controller.channel.event.PreloadDataContent;
|
||||
@@ -52,6 +53,7 @@ import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -72,9 +74,10 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
{
|
||||
private static final String DIVIDER = "-------------------------------------------------------------------------\n";
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelProcessingManager.class);
|
||||
private static final String TUNER_UNAVAILABLE_DESCRIPTION = "TUNER UNAVAILABLE";
|
||||
private Map<Channel,ProcessingChain> mProcessingChains = new ConcurrentHashMap<>();
|
||||
private Map<Channel,ProcessingChain> mProcessingChainsMap = new ConcurrentHashMap<>();
|
||||
private Lock mLock = new ReentrantLock();
|
||||
|
||||
private ChannelSourceEventErrorListener mSourceErrorListener = new ChannelSourceEventErrorListener();
|
||||
@@ -131,7 +134,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
isProcessing = mProcessingChains.containsKey(channel) && mProcessingChains.get(channel).isProcessing();
|
||||
isProcessing = mProcessingChainsMap.containsKey(channel) && mProcessingChainsMap.get(channel).isProcessing();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -147,7 +150,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
*/
|
||||
public boolean isProcessing()
|
||||
{
|
||||
return !mProcessingChains.isEmpty();
|
||||
return !mProcessingChainsMap.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +159,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
*/
|
||||
public ProcessingChain getProcessingChain(Channel channel)
|
||||
{
|
||||
return mProcessingChains.get(channel);
|
||||
return mProcessingChainsMap.get(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +178,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChains.entrySet())
|
||||
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChainsMap.entrySet())
|
||||
{
|
||||
if(entry.getValue() == processingChain)
|
||||
{
|
||||
@@ -206,7 +209,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChains.entrySet())
|
||||
for(Map.Entry<Channel,ProcessingChain> entry : mProcessingChainsMap.entrySet())
|
||||
{
|
||||
if(entry.getValue().hasSource(tunerChannelSource))
|
||||
{
|
||||
@@ -442,7 +445,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
/* Processing Modules */
|
||||
List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences,
|
||||
request.getTrafficChannelManager());
|
||||
request.getTrafficChannelManager(), request.getChannelDescriptor());
|
||||
processingChain.addModules(modules);
|
||||
|
||||
//Post preload data from the request to the event bus. Modules that can handle preload data will annotate
|
||||
@@ -556,10 +559,10 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
if(!mProcessingChains.containsKey(channel))
|
||||
if(!mProcessingChainsMap.containsKey(channel))
|
||||
{
|
||||
added = true;
|
||||
mProcessingChains.put(channel, processingChain);
|
||||
mProcessingChainsMap.put(channel, processingChain);
|
||||
getChannelMetadataModel().add(new ChannelAndMetadata(channel, processingChain.getChannelState().getChannelMetadata()));
|
||||
}
|
||||
}
|
||||
@@ -584,7 +587,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
removed = mProcessingChains.remove(channel);
|
||||
removed = mProcessingChainsMap.remove(channel);
|
||||
|
||||
if(removed != null)
|
||||
{
|
||||
@@ -633,6 +636,8 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
processingChain.removeFrequencyChangeListener(channel);
|
||||
channel.resetFrequencyCorrection();
|
||||
|
||||
//Notify all processing chains that this channel is shutting down so that if this is a traffic channel,
|
||||
//the owning parent channel's traffic channel manager can cleanup it's accounting.
|
||||
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
||||
mChannelEventBroadcaster.removeListener(processingChain);
|
||||
|
||||
@@ -660,7 +665,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
mDelayedChannelStartTasks.remove(delayedTask);
|
||||
}
|
||||
|
||||
List<Channel> channelsToStop = new ArrayList<>(mProcessingChains.keySet());
|
||||
List<Channel> channelsToStop = new ArrayList<>(mProcessingChainsMap.keySet());
|
||||
|
||||
for(Channel channel : channelsToStop)
|
||||
{
|
||||
@@ -683,7 +688,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
public void convertToTrafficChannel(ChannelConversionRequest request)
|
||||
{
|
||||
//Update the channel to processing chain map.
|
||||
ProcessingChain processingChain = mProcessingChains.remove(request.getCurrentChannel());
|
||||
ProcessingChain processingChain = mProcessingChainsMap.remove(request.getCurrentChannel());
|
||||
|
||||
if(processingChain != null)
|
||||
{
|
||||
@@ -697,7 +702,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
request.getTrafficChannel().setProcessing(true);
|
||||
});
|
||||
|
||||
mProcessingChains.put(request.getTrafficChannel(), processingChain);
|
||||
mProcessingChainsMap.put(request.getTrafficChannel(), processingChain);
|
||||
mChannelMetadataModel.updateChannelMetadataToChannelMap(processingChain.getChannelState().getChannelMetadata(),
|
||||
request.getTrafficChannel());
|
||||
|
||||
@@ -803,7 +808,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
|
||||
try
|
||||
{
|
||||
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChains.entrySet())
|
||||
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChainsMap.entrySet())
|
||||
{
|
||||
if(entry.getValue().hasSource(sourceEvent.getSource()))
|
||||
{
|
||||
@@ -841,4 +846,54 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a diagnostic report.
|
||||
* @return report text.
|
||||
*/
|
||||
public String getDiagnosticInformation()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Channel Processing Manager - Diagnostics Report\n\n");
|
||||
sb.append(DIVIDER);
|
||||
sb.append("\tChannel to Processing Chain Map Contents\n");
|
||||
Map<Channel,ProcessingChain> mapCopy = new HashMap<>(mProcessingChainsMap);
|
||||
for(Map.Entry<Channel,ProcessingChain> entry: mapCopy.entrySet())
|
||||
{
|
||||
sb.append("\n\n--------------- CHANNEL:PROCESSING CHAIN MAP ENTRY --------------------\n");
|
||||
try
|
||||
{
|
||||
Channel channel = entry.getKey();
|
||||
sb.append("\tChannel: " + channel).append("\n");
|
||||
sb.append("\t\tSource Configuration: " + channel.getSourceConfiguration()).append("\n");
|
||||
ProcessingChain chain = entry.getValue();
|
||||
sb.append("\tProcessing Chain - Processing: ").append(chain.isProcessing()).append("\n");
|
||||
AbstractChannelState state = chain.getChannelState();
|
||||
sb.append("Channel State: ").append(state.getClass()).append("\n");
|
||||
for(ChannelMetadata metadata: state.getChannelMetadata())
|
||||
{
|
||||
sb.append(metadata.getDescription()).append("\n");
|
||||
}
|
||||
|
||||
Source source = chain.getSource();
|
||||
|
||||
if(source != null)
|
||||
{
|
||||
sb.append("Channel Source Class: " + source.getClass()).append("\n");
|
||||
sb.append("\t\tTo String:").append(source).append("\n");
|
||||
sb.append("\t\tHash:").append(Integer.toHexString(source.hashCode()).toUpperCase()).append("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("Channel Source: (null)\n");
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
sb.append("\tError while logging diagnostics of map entry - " + t.getMessage()).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -147,6 +147,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
sb.append(" REQUESTED CF: ").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
|
||||
sb.append(" MIXER:").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
|
||||
sb.append(" | Polyphase Indices: ").append(indexes);
|
||||
sb.append(" HASH:").append(Integer.toHexString(pcs.hashCode()).toUpperCase());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package io.github.dsheirer.dsp.oscillator;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
@@ -45,7 +46,15 @@ public class VectorRealOscillator extends AbstractOscillator implements IRealOsc
|
||||
@Override
|
||||
public float[] generate(int sampleCount)
|
||||
{
|
||||
float[] samples = new float[sampleCount];
|
||||
int length = sampleCount;
|
||||
|
||||
//Increase the length until it aligns as a multiple of the vector species length
|
||||
while(length % VECTOR_SPECIES.length() != 0)
|
||||
{
|
||||
length++;
|
||||
}
|
||||
|
||||
float[] samples = new float[length];
|
||||
|
||||
for(int samplePointer = 0; samplePointer < sampleCount; samplePointer += VECTOR_SPECIES.length())
|
||||
{
|
||||
@@ -58,6 +67,7 @@ public class VectorRealOscillator extends AbstractOscillator implements IRealOsc
|
||||
generated.intoArray(samples, samplePointer);
|
||||
}
|
||||
|
||||
return samples;
|
||||
//Truncate the array to the requested length, if we increased it for SIMD alignment.
|
||||
return Arrays.copyOf(samples, sampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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 @@ import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.log.ApplicationLog;
|
||||
import io.github.dsheirer.map.MapService;
|
||||
import io.github.dsheirer.module.log.EventLogManager;
|
||||
import io.github.dsheirer.monitor.DiagnosticMonitor;
|
||||
import io.github.dsheirer.monitor.ResourceMonitor;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
@@ -125,6 +126,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
private AudioStreamingManager mAudioStreamingManager;
|
||||
private BroadcastStatusPanel mBroadcastStatusPanel;
|
||||
private ControllerPanel mControllerPanel;
|
||||
private DiagnosticMonitor mDiagnosticMonitor;
|
||||
private IconModel mIconModel = new IconModel();
|
||||
private PlaylistManager mPlaylistManager;
|
||||
private SettingsManager mSettingsManager;
|
||||
@@ -187,7 +189,13 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
EventLogManager eventLogManager = new EventLogManager(aliasModel, mUserPreferences);
|
||||
mPlaylistManager = new PlaylistManager(mUserPreferences, mTunerManager, aliasModel, eventLogManager, mIconModel);
|
||||
|
||||
if(!GraphicsEnvironment.isHeadless())
|
||||
boolean headless = GraphicsEnvironment.isHeadless();
|
||||
|
||||
mDiagnosticMonitor = new DiagnosticMonitor(mUserPreferences, mPlaylistManager.getChannelProcessingManager(),
|
||||
mTunerManager, headless);
|
||||
mDiagnosticMonitor.start();
|
||||
|
||||
if(!headless)
|
||||
{
|
||||
mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences, mTunerManager, mPlaylistManager);
|
||||
}
|
||||
@@ -411,6 +419,46 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
menuBar.add(fileMenu);
|
||||
|
||||
JMenuItem processingStatusReportMenuItem = new JMenuItem("Processing Diagnostic Report");
|
||||
processingStatusReportMenuItem.addActionListener(e -> {
|
||||
try
|
||||
{
|
||||
Path path = mDiagnosticMonitor.generateProcessingDiagnosticReport("User initiated diagnostic report");
|
||||
|
||||
JOptionPane.showMessageDialog(mMainGui, "Report created: " +
|
||||
path.toString(), "Processing Status Report Created", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error creating processing status report file", ioe);
|
||||
JOptionPane.showMessageDialog(mMainGui, "Unable to create report file. Please " +
|
||||
"see application log for details.", "Processing Status Report Failed", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem threadDumpReportMenuItem = new JMenuItem("Thread Dump Report");
|
||||
threadDumpReportMenuItem.addActionListener(e -> {
|
||||
try
|
||||
{
|
||||
Path path = mDiagnosticMonitor.generateThreadDumpReport();
|
||||
|
||||
JOptionPane.showMessageDialog(mMainGui, "Report created: " +
|
||||
path.toString(), "Thread Dump Report Created", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error creating thread dump report file", ioe);
|
||||
JOptionPane.showMessageDialog(mMainGui, "Unable to create report file. Please " +
|
||||
"see application log for details.", "Thread Dump Report Failed", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
|
||||
JMenu diagnosticMenu = new JMenu(("Reports"));
|
||||
diagnosticMenu.add(processingStatusReportMenuItem);
|
||||
diagnosticMenu.add(threadDumpReportMenuItem);
|
||||
fileMenu.add(diagnosticMenu);
|
||||
fileMenu.add(new JSeparator(JSeparator.HORIZONTAL));
|
||||
|
||||
JMenuItem exitMenu = new JMenuItem("Exit");
|
||||
exitMenu.addActionListener(event -> {
|
||||
processShutdown();
|
||||
@@ -578,6 +626,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
private void processShutdown()
|
||||
{
|
||||
mLog.info("Application shutdown started ...");
|
||||
mDiagnosticMonitor.stop();
|
||||
mUserPreferences.getSwingPreference().setLocation(WINDOW_FRAME_IDENTIFIER, mMainGui.getLocation());
|
||||
mUserPreferences.getSwingPreference().setDimension(WINDOW_FRAME_IDENTIFIER, mMainGui.getSize());
|
||||
mUserPreferences.getSwingPreference().setMaximized(WINDOW_FRAME_IDENTIFIER,
|
||||
|
||||
@@ -39,7 +39,7 @@ public class DbPowerMeter extends JComponent
|
||||
*/
|
||||
public DbPowerMeter()
|
||||
{
|
||||
setPreferredSize(new Dimension(80, 100));
|
||||
setPreferredSize(new Dimension(90, 100));
|
||||
setBorder(BorderFactory.createTitledBorder("Power (dB)"));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ package io.github.dsheirer.gui.preference;
|
||||
|
||||
import io.github.dsheirer.gui.preference.application.ApplicationPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.call.CallManagementPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.duplicate.DuplicateCallPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.mp3.MP3PreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.playback.PlaybackPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.record.RecordPreferenceEditor;
|
||||
@@ -42,8 +42,8 @@ public class PreferenceEditorFactory
|
||||
{
|
||||
case APPLICATION:
|
||||
return new ApplicationPreferenceEditor(userPreferences);
|
||||
case AUDIO_DUPLICATE_CALL_DETECTION:
|
||||
return new DuplicateCallPreferenceEditor(userPreferences);
|
||||
case AUDIO_CALL_MANAGEMENT:
|
||||
return new CallManagementPreferenceEditor(userPreferences);
|
||||
case AUDIO_MP3:
|
||||
return new MP3PreferenceEditor(userPreferences);
|
||||
case AUDIO_OUTPUT:
|
||||
|
||||
@@ -31,7 +31,7 @@ public enum PreferenceEditorType
|
||||
AUDIO_MP3("MP3"),
|
||||
AUDIO_RECORD("Record"),
|
||||
AUDIO_OUTPUT("Output/Tones"),
|
||||
AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"),
|
||||
AUDIO_CALL_MANAGEMENT("Call Management"),
|
||||
SOURCE_TUNERS("Tuners"),
|
||||
TALKGROUP_FORMAT("Talkgroup & Radio ID"),
|
||||
VECTOR_CALIBRATION("Vector Calibration"),
|
||||
|
||||
@@ -171,7 +171,7 @@ public class UserPreferencesEditor extends BorderPane
|
||||
applicationItem.setExpanded(true);
|
||||
|
||||
TreeItem<String> audioItem = new TreeItem<>("Audio");
|
||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_DUPLICATE_CALL_DETECTION));
|
||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_CALL_MANAGEMENT));
|
||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_MP3));
|
||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_OUTPUT));
|
||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_RECORD));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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,10 +23,16 @@ import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.application.ApplicationPreference;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
|
||||
/**
|
||||
@@ -38,6 +44,7 @@ public class ApplicationPreferenceEditor extends HBox
|
||||
private GridPane mEditorPane;
|
||||
private Label mAutoStartTimeoutLabel;
|
||||
private Spinner<Integer> mTimeoutSpinner;
|
||||
private ToggleSwitch mAutomaticDiagnosticMonitoringToggle;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -46,7 +53,14 @@ public class ApplicationPreferenceEditor extends HBox
|
||||
public ApplicationPreferenceEditor(UserPreferences userPreferences)
|
||||
{
|
||||
mApplicationPreference = userPreferences.getApplicationPreference();
|
||||
getChildren().add(getEditorPane());
|
||||
setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
vbox.setMaxHeight(Double.MAX_VALUE);
|
||||
vbox.setMaxWidth(Double.MAX_VALUE);
|
||||
vbox.getChildren().add(getEditorPane());
|
||||
HBox.setHgrow(vbox, Priority.ALWAYS);
|
||||
getChildren().add(vbox);
|
||||
}
|
||||
|
||||
private GridPane getEditorPane()
|
||||
@@ -55,13 +69,31 @@ public class ApplicationPreferenceEditor extends HBox
|
||||
{
|
||||
int row = 0;
|
||||
mEditorPane = new GridPane();
|
||||
mEditorPane.setMaxWidth(Double.MAX_VALUE);
|
||||
mEditorPane.setVgap(10);
|
||||
mEditorPane.setHgap(10);
|
||||
mEditorPane.setHgap(3);
|
||||
mEditorPane.setPadding(new Insets(10, 10, 10, 10));
|
||||
GridPane.setHalignment(getAutoStartTimeoutLabel(), HPos.RIGHT);
|
||||
mEditorPane.add(getAutoStartTimeoutLabel(), 0, row);
|
||||
mEditorPane.add(getTimeoutSpinner(), 1, row);
|
||||
mEditorPane.add(new Label("seconds"), 2, row);
|
||||
|
||||
Label monitoringLabel = new Label("Application Health and Diagnostic Monitoring.");
|
||||
mEditorPane.add(monitoringLabel, 0, row, 2, 1);
|
||||
GridPane.setHalignment(getAutomaticDiagnosticMonitoringToggle(), HPos.RIGHT);
|
||||
mEditorPane.add(getAutomaticDiagnosticMonitoringToggle(), 0, ++row);
|
||||
mEditorPane.add(new Label("Enable Diagnostic Monitoring"), 1, row, 2, 1);
|
||||
|
||||
Separator separator = new Separator(Orientation.HORIZONTAL);
|
||||
GridPane.setHgrow(separator, Priority.ALWAYS);
|
||||
mEditorPane.add(separator, 0, ++row, 3, 1);
|
||||
|
||||
mEditorPane.add(getAutoStartTimeoutLabel(), 0, ++row, 2, 1);
|
||||
GridPane.setHalignment(getTimeoutSpinner(), HPos.RIGHT);
|
||||
mEditorPane.add(getTimeoutSpinner(), 0, ++row);
|
||||
mEditorPane.add(new Label("seconds"), 1, row);
|
||||
|
||||
ColumnConstraints c1 = new ColumnConstraints();
|
||||
c1.setPercentWidth(30);
|
||||
ColumnConstraints c2 = new ColumnConstraints();
|
||||
c2.setHgrow(Priority.ALWAYS);
|
||||
mEditorPane.getColumnConstraints().addAll(c1, c2);
|
||||
}
|
||||
|
||||
return mEditorPane;
|
||||
@@ -91,4 +123,20 @@ public class ApplicationPreferenceEditor extends HBox
|
||||
|
||||
return mTimeoutSpinner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle switch to enable/disable automatic diagnostic monitoring.
|
||||
*/
|
||||
private ToggleSwitch getAutomaticDiagnosticMonitoringToggle()
|
||||
{
|
||||
if(mAutomaticDiagnosticMonitoringToggle == null)
|
||||
{
|
||||
mAutomaticDiagnosticMonitoringToggle = new ToggleSwitch();
|
||||
mAutomaticDiagnosticMonitoringToggle.setSelected(mApplicationPreference.isAutomaticDiagnosticMonitoring());
|
||||
mAutomaticDiagnosticMonitoringToggle.selectedProperty().addListener((observable, oldValue, enabled) ->
|
||||
mApplicationPreference.setAutomaticDiagnosticMonitoring(enabled));
|
||||
}
|
||||
|
||||
return mAutomaticDiagnosticMonitoringToggle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -17,12 +17,16 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.preference.duplicate;
|
||||
package io.github.dsheirer.gui.preference.call;
|
||||
|
||||
import io.github.dsheirer.audio.broadcast.PatchGroupStreamingOption;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
|
||||
import io.github.dsheirer.preference.duplicate.CallManagementPreference;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.GridPane;
|
||||
@@ -33,23 +37,24 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Preference settings for duplicate call audio handling
|
||||
* Preference settings for audio call management for duplicate calls and patch group streaming.
|
||||
*/
|
||||
public class DuplicateCallPreferenceEditor extends HBox
|
||||
public class CallManagementPreferenceEditor extends HBox
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallPreferenceEditor.class);
|
||||
private DuplicateCallDetectionPreference mPreference;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(CallManagementPreferenceEditor.class);
|
||||
private CallManagementPreference mPreference;
|
||||
private GridPane mEditorPane;
|
||||
private ToggleSwitch mDetectDuplicateTalkgroups;
|
||||
private ToggleSwitch mDetectDuplicateRadios;
|
||||
private ToggleSwitch mSuppressDuplicateListening;
|
||||
private ToggleSwitch mSuppressDuplicateRecording;
|
||||
private ToggleSwitch mSuppressDuplicateStreaming;
|
||||
private ComboBox<PatchGroupStreamingOption> mPatchGroupStreamingOptionComboBox;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DuplicateCallPreferenceEditor(UserPreferences userPreferences)
|
||||
public CallManagementPreferenceEditor(UserPreferences userPreferences)
|
||||
{
|
||||
mPreference = userPreferences.getDuplicateCallDetectionPreference();
|
||||
|
||||
@@ -134,6 +139,19 @@ public class DuplicateCallPreferenceEditor extends HBox
|
||||
streamingLabel.setWrapText(true);
|
||||
GridPane.setConstraints(streamingLabel, 1, row);
|
||||
mEditorPane.getChildren().add(streamingLabel);
|
||||
|
||||
Separator separator2 = new Separator();
|
||||
GridPane.setHgrow(separator2, Priority.ALWAYS);
|
||||
GridPane.setConstraints(separator2, 0, ++row, 2, 1);
|
||||
mEditorPane.getChildren().add(separator2);
|
||||
|
||||
Label patchGroupLabel = new Label("Patch Group Streaming. Stream a patch group call as:");
|
||||
patchGroupLabel.setWrapText(true);
|
||||
GridPane.setConstraints(patchGroupLabel, 0, ++row, 2, 1);
|
||||
mEditorPane.getChildren().add(patchGroupLabel);
|
||||
|
||||
GridPane.setConstraints(getPatchGroupStreamingOptionComboBox(), 0, ++row, 2, 1);
|
||||
mEditorPane.getChildren().add(getPatchGroupStreamingOptionComboBox());
|
||||
}
|
||||
|
||||
return mEditorPane;
|
||||
@@ -212,4 +230,23 @@ public class DuplicateCallPreferenceEditor extends HBox
|
||||
|
||||
return mSuppressDuplicateStreaming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combo box for presenting the patch group streaming options.
|
||||
* @return combo box.
|
||||
*/
|
||||
private ComboBox<PatchGroupStreamingOption> getPatchGroupStreamingOptionComboBox()
|
||||
{
|
||||
if(mPatchGroupStreamingOptionComboBox == null)
|
||||
{
|
||||
ObservableList<PatchGroupStreamingOption> options = FXCollections.observableArrayList();
|
||||
options.addAll(PatchGroupStreamingOption.values());
|
||||
mPatchGroupStreamingOptionComboBox = new ComboBox<>(options);
|
||||
mPatchGroupStreamingOptionComboBox.getSelectionModel().select(mPreference.getPatchGroupStreamingOption());
|
||||
mPatchGroupStreamingOptionComboBox.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> mPreference.setPatchGroupStreamingOption(newValue));
|
||||
}
|
||||
|
||||
return mPatchGroupStreamingOptionComboBox;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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.log;
|
||||
@@ -30,16 +29,15 @@ import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Logback and SLF4j logging implementation.
|
||||
@@ -182,7 +180,11 @@ public class ApplicationLog
|
||||
}
|
||||
}
|
||||
|
||||
private Attributes findManifestAttributes() {
|
||||
/**
|
||||
* Finds the jar manifest attributes
|
||||
* @return attributes or null.
|
||||
*/
|
||||
public Attributes findManifestAttributes() {
|
||||
try {
|
||||
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
@@ -197,9 +199,12 @@ public class ApplicationLog
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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,6 +23,7 @@ import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.alias.action.AliasActionManager;
|
||||
import io.github.dsheirer.audio.AbstractAudioModule;
|
||||
import io.github.dsheirer.audio.AudioModule;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.channel.state.State;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
@@ -47,7 +48,11 @@ 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.channel.DMRChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
|
||||
import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState;
|
||||
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
|
||||
@@ -120,9 +125,11 @@ public class DecoderFactory
|
||||
* @return list of configured decoders
|
||||
*/
|
||||
public static List<Module> getModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel,
|
||||
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager)
|
||||
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager,
|
||||
IChannelDescriptor channelDescriptor)
|
||||
{
|
||||
List<Module> modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences, trafficChannelManager);
|
||||
List<Module> modules = getPrimaryModules(channelMapModel, channel, aliasModel, userPreferences,
|
||||
trafficChannelManager, channelDescriptor);
|
||||
modules.addAll(getAuxiliaryDecoders(channel.getAuxDecodeConfiguration()));
|
||||
return modules;
|
||||
}
|
||||
@@ -135,10 +142,12 @@ public class DecoderFactory
|
||||
* @param aliasModel for alias lookups
|
||||
* @param userPreferences instance
|
||||
* @param trafficChannelManager optional traffic channel manager to use
|
||||
* @param channelDescriptor to preload into the decoder state as the current channel.
|
||||
* @return list of modules to use for a processing chain
|
||||
*/
|
||||
public static List<Module> getPrimaryModules(ChannelMapModel channelMapModel, Channel channel, AliasModel aliasModel,
|
||||
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager)
|
||||
UserPreferences userPreferences, TrafficChannelManager trafficChannelManager,
|
||||
IChannelDescriptor channelDescriptor)
|
||||
{
|
||||
List<Module> modules = new ArrayList<Module>();
|
||||
|
||||
@@ -157,7 +166,7 @@ public class DecoderFactory
|
||||
break;
|
||||
case DMR:
|
||||
processDMR(channel, userPreferences, modules, aliasList, (DecodeConfigDMR)decodeConfig,
|
||||
trafficChannelManager);
|
||||
trafficChannelManager, channelDescriptor);
|
||||
break;
|
||||
case NBFM:
|
||||
processNBFM(channel, modules, aliasList, decodeConfig);
|
||||
@@ -410,28 +419,82 @@ public class DecoderFactory
|
||||
*/
|
||||
private static void processDMR(Channel channel, UserPreferences userPreferences, List<Module> modules,
|
||||
AliasList aliasList, DecodeConfigDMR decodeConfig,
|
||||
TrafficChannelManager trafficChannelManager)
|
||||
TrafficChannelManager trafficChannelManager, IChannelDescriptor channelDescriptor)
|
||||
{
|
||||
modules.add(new DMRDecoder(decodeConfig));
|
||||
|
||||
DMRTrafficChannelManager dmrTrafficChannelManager = null;
|
||||
|
||||
if(trafficChannelManager instanceof DMRTrafficChannelManager)
|
||||
{
|
||||
dmrTrafficChannelManager = (DMRTrafficChannelManager)trafficChannelManager;
|
||||
}
|
||||
else
|
||||
{
|
||||
dmrTrafficChannelManager = new DMRTrafficChannelManager(channel);
|
||||
}
|
||||
|
||||
//Only register the traffic channel manager as a module if this is the parent control channel.
|
||||
if(channel.isStandardChannel())
|
||||
{
|
||||
if(trafficChannelManager instanceof DMRTrafficChannelManager)
|
||||
{
|
||||
dmrTrafficChannelManager = (DMRTrafficChannelManager)trafficChannelManager;
|
||||
}
|
||||
else
|
||||
{
|
||||
dmrTrafficChannelManager = new DMRTrafficChannelManager(channel);
|
||||
}
|
||||
|
||||
modules.add(dmrTrafficChannelManager);
|
||||
}
|
||||
|
||||
modules.add(new DMRDecoderState(channel, 1, dmrTrafficChannelManager));
|
||||
modules.add(new DMRDecoderState(channel, 2, dmrTrafficChannelManager));
|
||||
DMRDecoderState state1 = new DMRDecoderState(channel, 1, dmrTrafficChannelManager);
|
||||
DMRDecoderState state2 = new DMRDecoderState(channel, 2, dmrTrafficChannelManager);
|
||||
|
||||
//Register the states with each other so that they can pass Cap+ site status messaging to resolve current channel
|
||||
state1.setSisterDecoderState(state2);
|
||||
state2.setSisterDecoderState(state1);
|
||||
|
||||
//If an LSN is provided, apply it to both of the decoder states.
|
||||
if(channelDescriptor instanceof DMRLsn lsn)
|
||||
{
|
||||
//If this is a REST descriptor, change it to a standard LSN descriptor.
|
||||
if(channelDescriptor instanceof DmrRestLsn rest)
|
||||
{
|
||||
lsn = new DMRLsn(rest.getLsn());
|
||||
lsn.setTimeslotFrequency(rest.getTimeslotFrequency());
|
||||
}
|
||||
|
||||
if(lsn.getTimeslot() == 1)
|
||||
{
|
||||
state1.setCurrentChannel(lsn);
|
||||
state2.setCurrentChannel(lsn.getSisterTimeslot());
|
||||
}
|
||||
else
|
||||
{
|
||||
state1.setCurrentChannel(lsn.getSisterTimeslot());
|
||||
state2.setCurrentChannel(lsn);
|
||||
}
|
||||
}
|
||||
|
||||
if(decodeConfig.hasChannelGrantEvent())
|
||||
{
|
||||
DecodeEvent event = decodeConfig.getChannelGrantEvent();
|
||||
|
||||
if(decodeConfig.getChannelGrantEvent().getTimeslot() == 1)
|
||||
{
|
||||
state1.setCurrentCallEvent(event);
|
||||
|
||||
if(event.getChannelDescriptor() instanceof DMRChannel dmrChannel)
|
||||
{
|
||||
state2.setCurrentChannel(dmrChannel.getSisterTimeslot());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state2.setCurrentCallEvent(event);
|
||||
|
||||
if(event.getChannelDescriptor() instanceof DMRChannel dmrChannel)
|
||||
{
|
||||
state1.setCurrentChannel(dmrChannel.getSisterTimeslot());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modules.add(state1);
|
||||
modules.add(state2);
|
||||
modules.add(new DMRAudioModule(userPreferences, aliasList, 1));
|
||||
modules.add(new DMRAudioModule(userPreferences, aliasList, 2));
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -145,11 +145,10 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
getIdentifierCollection().update(getChannelNameIdentifier());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {}
|
||||
@Override
|
||||
public void init() {}
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -115,9 +115,4 @@ public class DCSDecoderState extends DecoderState
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -33,17 +33,24 @@ import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.log.LoggingSuppressor;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
|
||||
import io.github.dsheirer.module.decode.dmr.event.DMRDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||
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.data.csbk.UnknownCSBKMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraTrafficChannelTalkerStatus;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAdvantageModeVoiceChannelUpdate;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxOpenModeVoiceChannelUpdate;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusNeighbors;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusSiteStatus;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.ConnectPlusDataChannelGrant;
|
||||
@@ -66,6 +73,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasComp
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAlias;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAliasContinuation;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||
@@ -80,6 +90,7 @@ import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParam
|
||||
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
|
||||
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||
@@ -91,7 +102,6 @@ import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||
import io.github.dsheirer.util.PacketUtil;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
@@ -105,16 +115,17 @@ import org.slf4j.LoggerFactory;
|
||||
public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRDecoderState.class);
|
||||
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(mLog);
|
||||
private static final long MAX_VALID_CALL_DURATION_MS = 30000;
|
||||
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
||||
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
||||
private Channel mChannel;
|
||||
private DMRNetworkConfigurationMonitor mNetworkConfigurationMonitor;
|
||||
private DMRTrafficChannelManager mTrafficChannelManager;
|
||||
private DecodeEvent mCurrentCallEvent;
|
||||
private long mCurrentFrequency;
|
||||
private boolean mIgnoreCRCChecksums;
|
||||
private Map<DMRChannel,DecodeEvent> mDetectedCallEventsMap = new HashMap<>();
|
||||
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
|
||||
new AddChannelRotationActiveStateRequest(State.ACTIVE);
|
||||
private DMRDecoderState mSisterDecoderState;
|
||||
|
||||
/**
|
||||
* Constructs an DMR decoder state with an optional traffic channel manager.
|
||||
@@ -142,6 +153,75 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reference to the decoder state that is processing the sisten timeslot. If this state is covering
|
||||
* timeslot 1, then the argument will be the state covering timeslot 2, and vice-versa.
|
||||
*/
|
||||
public void setSisterDecoderState(DMRDecoderState state)
|
||||
{
|
||||
mSisterDecoderState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a map of active talkgroups received from the sister timeslot decoder state so that we can recover
|
||||
* the current channel identifier to enable populating call events with accurate current channel info.
|
||||
*
|
||||
* This is used for Capacity Plus systems to recover the current channel.
|
||||
*
|
||||
* @param idMap to process
|
||||
* @param lsnMap to process
|
||||
*/
|
||||
public DMRChannel processActiveTalkgroups(Map<Integer, IntegerIdentifier> idMap, Map<Integer, DMRLsn> lsnMap)
|
||||
{
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
List<Identifier> toIds = mCurrentCallEvent.getIdentifierCollection().getIdentifiers(Role.TO);
|
||||
|
||||
if(toIds.size() >= 1)
|
||||
{
|
||||
Identifier to = toIds.get(0);
|
||||
|
||||
if(to instanceof TalkgroupIdentifier talkgroup)
|
||||
{
|
||||
Integer tgValue = talkgroup.getValue();
|
||||
|
||||
for(Map.Entry<Integer,IntegerIdentifier> entry: idMap.entrySet())
|
||||
{
|
||||
if(tgValue != null && tgValue.equals(entry.getValue().getValue()))
|
||||
{
|
||||
DMRLsn lsn = lsnMap.get(entry.getKey());
|
||||
setCurrentChannel(lsn);
|
||||
return lsn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void broadcast(IDecodeEvent event)
|
||||
{
|
||||
super.broadcast(event);
|
||||
|
||||
if(mChannel.isTrafficChannel() && hasTrafficChannelManager())
|
||||
{
|
||||
mTrafficChannelManager.receiveTrafficChannelEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
|
||||
* @param message to check
|
||||
* @return true if ignore CRC checksums or if the message is valid, meaning the message has passed CRC check.
|
||||
*/
|
||||
private boolean isValid(IMessage message)
|
||||
{
|
||||
return mIgnoreCRCChecksums || message.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this decoder state has an (optional) traffic channel manager.
|
||||
*/
|
||||
@@ -150,6 +230,16 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
return mTrafficChannelManager != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial call event for a traffic channel.
|
||||
* @param decodeEvent to use as the initial call event.
|
||||
*/
|
||||
public void setCurrentCallEvent(DecodeEvent decodeEvent)
|
||||
{
|
||||
mCurrentCallEvent = decodeEvent;
|
||||
setCurrentChannel(decodeEvent.getChannelDescriptor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes channel configuration change notifications received over the processing chain event bus. This is
|
||||
* primarily used for Capacity+ systems when the standard channel is converted to a traffic channel. In response,
|
||||
@@ -186,7 +276,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
resetState();
|
||||
|
||||
mCurrentFrequency = 0;
|
||||
mDetectedCallEventsMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,21 +295,21 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
if(message.getTimeslot() == getTimeslot())
|
||||
{
|
||||
if(message instanceof VoiceMessage)
|
||||
if(message instanceof VoiceMessage voice)
|
||||
{
|
||||
processVoice((VoiceMessage)message);
|
||||
processVoice(voice);
|
||||
}
|
||||
else if(message instanceof DataMessage)
|
||||
else if(message instanceof DataMessage data)
|
||||
{
|
||||
processData((DataMessage)message);
|
||||
processData(data);
|
||||
}
|
||||
else if((message.isValid() || mIgnoreCRCChecksums) && message instanceof LCMessage)
|
||||
else if(isValid(message) && message instanceof LCMessage lcMessage)
|
||||
{
|
||||
processLinkControl((LCMessage)message, false);
|
||||
processLinkControl(lcMessage, false);
|
||||
}
|
||||
else if(message.isValid() && message instanceof DMRPacketMessage)
|
||||
else if(isValid(message) && message instanceof DMRPacketMessage packet)
|
||||
{
|
||||
processPacket((DMRPacketMessage)message);
|
||||
processPacket(packet);
|
||||
}
|
||||
else if(message instanceof UDTShortMessageService sms)
|
||||
{
|
||||
@@ -232,16 +321,15 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
//SLCO messages on timeslot 0 to catch capacity plus rest channel events
|
||||
else if((message.isValid() || mIgnoreCRCChecksums) && message.getTimeslot() == 0 && message instanceof LCMessage)
|
||||
else if(isValid(message) && message.getTimeslot() == 0 && message instanceof LCMessage lcMessage)
|
||||
{
|
||||
processLinkControl((LCMessage)message, false);
|
||||
processLinkControl(lcMessage, false);
|
||||
}
|
||||
|
||||
//Pass the message to the network configuration monitor, if this decoder state has a non-null instance
|
||||
if(mNetworkConfigurationMonitor != null && (message.isValid() || mIgnoreCRCChecksums) &&
|
||||
message instanceof DMRMessage)
|
||||
if(mNetworkConfigurationMonitor != null && isValid(message) && message instanceof DMRMessage dmrMessage)
|
||||
{
|
||||
mNetworkConfigurationMonitor.process((DMRMessage)message);
|
||||
mNetworkConfigurationMonitor.process(dmrMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,6 +385,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details("MESSAGE: " + sms.getSMS())
|
||||
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
@@ -321,6 +410,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
mic.update(hyteraSmsPacket.getDestination());
|
||||
|
||||
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details("SMS:" + hyteraSmsPacket.getSMS())
|
||||
@@ -336,6 +426,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
sb.append("HYTERA RRS REGISTER RADIO:");
|
||||
sb.append(rrs.getDestination());
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(sb.toString())
|
||||
@@ -361,6 +452,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(sb.toString())
|
||||
@@ -373,6 +465,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details("HYTERA LONG DATA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||
@@ -385,6 +478,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(ars.toString())
|
||||
@@ -397,6 +491,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(lrrp.toString())
|
||||
@@ -409,6 +504,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||
|
||||
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(mic)
|
||||
.timeslot(getTimeslot())
|
||||
.details(xcmp.toString())
|
||||
@@ -418,6 +514,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
else
|
||||
{
|
||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.details(packet.toString())
|
||||
@@ -503,7 +600,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
//Process the link control message to get the identifiers
|
||||
LCMessage lc = header.getLCMessage();
|
||||
|
||||
if(lc.isValid())
|
||||
if(isValid(lc))
|
||||
{
|
||||
processLinkControl(lc, false);
|
||||
}
|
||||
@@ -520,15 +617,15 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
switch(message.getSlotType().getDataType())
|
||||
{
|
||||
case CSBK:
|
||||
if((message.isValid() || mIgnoreCRCChecksums) && message instanceof CSBKMessage)
|
||||
if(isValid(message) && message instanceof CSBKMessage csbk)
|
||||
{
|
||||
processCSBK((CSBKMessage)message);
|
||||
processCSBK(csbk);
|
||||
}
|
||||
break;
|
||||
case VOICE_HEADER:
|
||||
if(message instanceof HeaderMessage)
|
||||
if(message instanceof HeaderMessage header)
|
||||
{
|
||||
processVoiceHeader((HeaderMessage)message);
|
||||
processVoiceHeader(header);
|
||||
}
|
||||
break;
|
||||
case USB_DATA:
|
||||
@@ -539,9 +636,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
case MBC_ENC_HEADER:
|
||||
case DATA_ENC_HEADER:
|
||||
case CHANNEL_CONTROL_ENC_HEADER:
|
||||
if(message instanceof HeaderMessage)
|
||||
if(message instanceof HeaderMessage header)
|
||||
{
|
||||
processHeader((HeaderMessage)message);
|
||||
processHeader(header);
|
||||
}
|
||||
break;
|
||||
case SLOT_IDLE:
|
||||
@@ -579,7 +676,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
LCMessage lcMessage = terminator.getLCMessage();
|
||||
|
||||
if(lcMessage.isValid())
|
||||
if(isValid(lcMessage))
|
||||
{
|
||||
processLinkControl(lcMessage, true);
|
||||
}
|
||||
@@ -592,7 +689,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
LCMessage lcMessage = voiceHeader.getLCMessage();
|
||||
|
||||
if(lcMessage.isValid())
|
||||
if(isValid(lcMessage))
|
||||
{
|
||||
processLinkControl(lcMessage, false);
|
||||
}
|
||||
@@ -745,20 +842,36 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
//Update state and rest channel
|
||||
updateRestChannel(cpss.getRestChannel());
|
||||
|
||||
//If the sister timeslot hasn't identified the current channel, attempt to identify the channel
|
||||
//from the current call activity map.
|
||||
if(cpss.hasVoiceTalkgroups() && mSisterDecoderState != null && mSisterDecoderState.getCurrentChannel() == null)
|
||||
{
|
||||
DMRChannel sisterChannel = mSisterDecoderState
|
||||
.processActiveTalkgroups(cpss.getActiveIdentifierMap(), cpss.getActiveLsnMap());
|
||||
|
||||
//If the returned channel is non-null, set our own channel
|
||||
if(getCurrentChannel() == null && sisterChannel != null)
|
||||
{
|
||||
setCurrentChannel(sisterChannel.getSisterTimeslot());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT:
|
||||
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
||||
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM:
|
||||
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
||||
if(csbk instanceof ChannelGrant)
|
||||
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
if(csbk instanceof ChannelGrant channelGrant)
|
||||
{
|
||||
ChannelGrant dataGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = dataGrant.getChannel();
|
||||
|
||||
DMRChannel channel = channelGrant.getChannel();
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
@@ -768,89 +881,17 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
event.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
if(csbk instanceof ChannelGrant)
|
||||
else
|
||||
{
|
||||
ChannelGrant tgGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = tgGrant.getChannel();
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
//Log when a CSBK that is not the Unknown CSBK is processed, to detect when new opcodes are added
|
||||
//that are not ChannelGrant subclass implementations.
|
||||
if(!(csbk instanceof UnknownCSBKMessage))
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
event.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||
if(csbk instanceof ChannelGrant)
|
||||
{
|
||||
ChannelGrant channelGrant = (ChannelGrant)csbk;
|
||||
DMRChannel channel = channelGrant.getChannel();
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
|
||||
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
event.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
mLog.error("Unrecognized DMR channel grant CSBK ignored: " + csbk.getClass());
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -867,42 +908,113 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT:
|
||||
if(csbk instanceof ConnectPlusDataChannelGrant)
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||
if(csbk instanceof CapacityMaxOpenModeVoiceChannelUpdate update)
|
||||
{
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
if(update.hasTimeslot1())
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
mic.remove(IdentifierClass.USER);
|
||||
mic.update(update.getChannelTS1());
|
||||
mic.update(update.getTalkgroupTS1());
|
||||
mTrafficChannelManager.processChannelGrant(update.getChannelTS1(), mic, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
if(update.hasTimeslot2())
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
mic.remove(IdentifierClass.USER);
|
||||
mic.update(update.getChannelTS2());
|
||||
mic.update(update.getTalkgroupTS2());
|
||||
mTrafficChannelManager.processChannelGrant(update.getChannelTS2(), mic, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||
}
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE:
|
||||
if(csbk instanceof CapacityMaxAdvantageModeVoiceChannelUpdate update)
|
||||
{
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
if(update.hasChannel1Timeslot1())
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
mic.remove(IdentifierClass.USER);
|
||||
mic.update(update.getChannel1TS1());
|
||||
mic.update(update.getTalkgroupCH1TS1());
|
||||
mTrafficChannelManager.processChannelGrant(update.getChannel1TS1(), mic, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
|
||||
if(update.hasChannel1Timeslot2())
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
mic.remove(IdentifierClass.USER);
|
||||
mic.update(update.getChannel1TS2());
|
||||
mic.update(update.getTalkgroupCH1TS2());
|
||||
mTrafficChannelManager.processChannelGrant(update.getChannel1TS2(), mic, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
|
||||
if(update.hasChannel2Timeslot1())
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
mic.remove(IdentifierClass.USER);
|
||||
mic.update(update.getChannel2TS1());
|
||||
mic.update(update.getTalkgroupCH2TS1());
|
||||
mTrafficChannelManager.processChannelGrant(update.getChannel2TS1(), mic, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
|
||||
if(update.hasChannel2Timeslot2())
|
||||
{
|
||||
MutableIdentifierCollection mic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
|
||||
mic.remove(IdentifierClass.USER);
|
||||
mic.update(update.getChannel2TS2());
|
||||
mic.update(update.getTalkgroupCH2TS2());
|
||||
mTrafficChannelManager.processChannelGrant(update.getChannel2TS2(), mic, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||
}
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT:
|
||||
if(csbk instanceof ConnectPlusDataChannelGrant cpdcg)
|
||||
{
|
||||
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
|
||||
DMRChannel channel = cpdcg.getChannel();
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent event = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
event.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||
}
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
|
||||
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details("Registration Request")
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
@@ -912,6 +1024,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
|
||||
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details("Registration Response")
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
@@ -920,41 +1033,27 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
|
||||
if(csbk instanceof ConnectPlusVoiceChannelUser)
|
||||
if(csbk instanceof ConnectPlusVoiceChannelUser cpvcu)
|
||||
{
|
||||
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
|
||||
DMRChannel channel = cpvcu.getChannel();
|
||||
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
|
||||
|
||||
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
|
||||
csbk.getTimestamp(), csbk.isEncrypted());
|
||||
}
|
||||
else
|
||||
{
|
||||
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel);
|
||||
|
||||
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
|
||||
{
|
||||
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
|
||||
new IdentifierCollection(csbk.getIdentifiers()));
|
||||
mDetectedCallEventsMap.put(channel, detectedEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Update the ending timestamp for the event and rebroadcast
|
||||
detectedEvent.end(csbk.getTimestamp());
|
||||
}
|
||||
|
||||
broadcast(detectedEvent);
|
||||
LOGGING_SUPPRESSOR.error("NoTCM" + csbk.getOpcode().name(), 2,
|
||||
"No DMR Traffic Channel Manager available for channel grant-" + csbk.getOpcode().name());
|
||||
}
|
||||
}
|
||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
|
||||
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.details("TALKGROUP AFFILIATION")
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
@@ -968,18 +1067,9 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel,
|
||||
IdentifierCollection identifierCollection) {
|
||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||
.channel(channel)
|
||||
.details(csbk.getOpcode().getLabel())
|
||||
.identifiers(identifierCollection)
|
||||
.timeslot(getTimeslot())
|
||||
.build();
|
||||
}
|
||||
|
||||
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
|
||||
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
|
||||
.channel(getCurrentChannel())
|
||||
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
|
||||
.timeslot(getTimeslot())
|
||||
.details(details)
|
||||
@@ -1000,82 +1090,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
return mic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the event is a stale event, meaning that the event is null, or the event start exceeds the max
|
||||
* valid call duration threshold, or if the event identifiers don't match the current identifiers.
|
||||
*
|
||||
* @param event to check for staleness
|
||||
* @param timestamp to check the event against
|
||||
* @param currentIdentifiers to compare against the event
|
||||
* @return true if the event is stale.
|
||||
*/
|
||||
private boolean isStale(DecodeEvent event, long timestamp, List<Identifier> currentIdentifiers)
|
||||
{
|
||||
if(event == null || (timestamp - event.getTimeStart() > MAX_VALID_CALL_DURATION_MS))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !isSameCall(event.getIdentifierCollection(), currentIdentifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the TO/FROM identifiers in the identifier collection match the TO/FROM identifiers in the list
|
||||
* of identifiers.
|
||||
* @param identifierCollection containing TO/FROM identifiers
|
||||
* @param identifiers containing TO/FROM identifiers
|
||||
* @return true if the TO/FROM identifiers in each collection match
|
||||
*/
|
||||
private boolean isSameCall(IdentifierCollection identifierCollection, List<Identifier> identifiers)
|
||||
{
|
||||
IntegerIdentifier to = null;
|
||||
IntegerIdentifier from = null;
|
||||
|
||||
for(Identifier identifier: identifierCollection.getIdentifiers(IdentifierClass.USER, Role.TO))
|
||||
{
|
||||
if(identifier instanceof IntegerIdentifier)
|
||||
{
|
||||
to = (IntegerIdentifier)identifier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(Identifier identifier: identifierCollection.getIdentifiers(IdentifierClass.USER, Role.FROM))
|
||||
{
|
||||
if(identifier instanceof IntegerIdentifier)
|
||||
{
|
||||
from = (IntegerIdentifier)identifier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(to == null || from == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean toMatch = false;
|
||||
boolean fromMatch = false;
|
||||
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
if(identifier.getRole() == Role.TO &&
|
||||
identifier instanceof IntegerIdentifier &&
|
||||
((IntegerIdentifier)identifier).getValue() == to.getValue())
|
||||
{
|
||||
toMatch = true;
|
||||
}
|
||||
else if(identifier.getRole() == Role.FROM &&
|
||||
identifier instanceof IntegerIdentifier &&
|
||||
((IntegerIdentifier)identifier).getValue() == from.getValue())
|
||||
{
|
||||
fromMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
return toMatch & fromMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes Link Control Messages
|
||||
* @param isTerminator set to true when the link control is carried by a terminator
|
||||
@@ -1090,6 +1104,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
mCurrentCallEvent.setDetails(ep.getDetails());
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1104,6 +1119,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cpgvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1121,6 +1137,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cpgvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1133,6 +1150,77 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
||||
if(message instanceof CapacityMaxVoiceChannelUser cmvcu)
|
||||
{
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cmvcu.getTalkgroup());
|
||||
}
|
||||
else
|
||||
{
|
||||
getIdentifierCollection().update(message.getIdentifiers());
|
||||
ServiceOptions serviceOptions = cmvcu.getServiceOptions();
|
||||
updateCurrentCall(serviceOptions.isEncrypted() ? DecodeEventType.CALL_GROUP_ENCRYPTED :
|
||||
DecodeEventType.CALL_GROUP, serviceOptions.toString(), message.getTimestamp());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS:
|
||||
if(message instanceof CapacityMaxTalkerAlias alias)
|
||||
{
|
||||
//If we have a talker alias identifier, append this value.
|
||||
Identifier existing = getIdentifierCollection().getIdentifier(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
|
||||
|
||||
if(existing instanceof DmrTalkerAliasIdentifier talkerAlias &&
|
||||
!talkerAlias.equals(alias.getTalkerAliasIdentifier()) &&
|
||||
!talkerAlias.getValue().contains(alias.getTalkerAliasIdentifier().getValue()))
|
||||
{
|
||||
//Concatenate the existing talker alias fragment with the base alias value.
|
||||
DmrTalkerAliasIdentifier updated = DmrTalkerAliasIdentifier
|
||||
.create(alias.getTalkerAliasIdentifier().getValue() + talkerAlias.getValue());
|
||||
getIdentifierCollection().update(updated);
|
||||
}
|
||||
else
|
||||
{
|
||||
getIdentifierCollection().update(alias.getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
|
||||
if(message instanceof CapacityMaxTalkerAliasContinuation alias)
|
||||
{
|
||||
//If we have a talker alias identifier, append this value.
|
||||
Identifier existing = getIdentifierCollection().getIdentifier(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
|
||||
|
||||
if(existing instanceof DmrTalkerAliasIdentifier talkerAlias &&
|
||||
!talkerAlias.equals(alias.getTalkerAliasIdentifier()) &&
|
||||
!talkerAlias.getValue().contains(alias.getTalkerAliasIdentifier().getValue()))
|
||||
{
|
||||
//Concatenate the existing talker alias value with the updated continuation fragment.
|
||||
DmrTalkerAliasIdentifier updated = DmrTalkerAliasIdentifier.create(talkerAlias.getValue() +
|
||||
alias.getTalkerAliasIdentifier().getValue());
|
||||
getIdentifierCollection().update(updated);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Temporarily place the continuation fragment alias into the identifier collection.
|
||||
getIdentifierCollection().update(alias.getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
if(mCurrentCallEvent != null)
|
||||
{
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER:
|
||||
if(message instanceof CapacityPlusWideAreaVoiceChannelUser)
|
||||
{
|
||||
@@ -1142,6 +1230,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(cpuo4.getTalkgroup());
|
||||
}
|
||||
@@ -1158,6 +1247,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(hgvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1182,6 +1272,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(huuvcu.getTargetRadio());
|
||||
}
|
||||
@@ -1201,6 +1292,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(gvcu.getTalkgroup());
|
||||
}
|
||||
@@ -1220,6 +1312,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
if(isTerminator)
|
||||
{
|
||||
closeCurrentCallEvent(message.getTimestamp());
|
||||
getIdentifierCollection().remove(Role.FROM);
|
||||
getIdentifierCollection().update(uuvcu.getTargetRadio());
|
||||
}
|
||||
@@ -1309,8 +1402,6 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
.identifiers(getIdentifierCollection().copyOf())
|
||||
.timeslot(getTimeslot())
|
||||
.build();
|
||||
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1321,9 +1412,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
|
||||
mCurrentCallEvent.setIdentifierCollection(getIdentifierCollection().copyOf());
|
||||
mCurrentCallEvent.end(timestamp);
|
||||
broadcast(mCurrentCallEvent);
|
||||
}
|
||||
|
||||
broadcast(mCurrentCallEvent);
|
||||
|
||||
if(type == DecodeEventType.CALL_GROUP_ENCRYPTED || type == DecodeEventType.CALL_UNIT_TO_UNIT_ENCRYPTED)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, event, State.ENCRYPTED, getTimeslot()));
|
||||
@@ -1347,6 +1439,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
broadcast(mCurrentCallEvent);
|
||||
mCurrentCallEvent = null;
|
||||
}
|
||||
|
||||
getIdentifierCollection().remove(IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1367,16 +1461,13 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
{
|
||||
case REQUEST_RESET:
|
||||
resetState();
|
||||
if(mNetworkConfigurationMonitor != null)
|
||||
{
|
||||
mNetworkConfigurationMonitor.reset();
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_SOURCE_FREQUENCY:
|
||||
long previous = mCurrentFrequency;
|
||||
mCurrentFrequency = event.getFrequency();
|
||||
if(hasTrafficChannelManager())
|
||||
{
|
||||
mTrafficChannelManager.setCurrentControlFrequency(mCurrentFrequency);
|
||||
mTrafficChannelManager.setCurrentControlFrequency(previous, mCurrentFrequency, mChannel);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1387,6 +1478,8 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
|
||||
//Change the default (45-second) traffic channel timeout to 1 second
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
@@ -1398,9 +1491,4 @@ public class DMRDecoderState extends TimeslotDecoderState
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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,6 +25,7 @@ import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessageWithLinkControl;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.IDLEMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
@@ -66,13 +67,14 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
|
||||
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
||||
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
||||
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
||||
private MBCAssembler mMBCAssembler;
|
||||
private PacketSequenceAssembler mPacketSequenceAssembler;
|
||||
private SLCAssembler mSLCAssembler = new SLCAssembler();
|
||||
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
|
||||
private Listener<IMessage> mMessageListener;
|
||||
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
||||
private DmrCrcMaskManager mCrcMaskManager = new DmrCrcMaskManager();
|
||||
private boolean mIgnoreCrcChecksums;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -80,6 +82,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
public DMRMessageProcessor(DecodeConfigDMR config)
|
||||
{
|
||||
mConfigDMR = config;
|
||||
mIgnoreCrcChecksums = config.getIgnoreCRCChecksums();
|
||||
mMBCAssembler = new MBCAssembler(mIgnoreCrcChecksums);
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
|
||||
{
|
||||
@@ -89,6 +93,17 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
mPacketSequenceAssembler = new PacketSequenceAssembler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
|
||||
*
|
||||
* @param message to check
|
||||
* @return true if ignore CRC checksums or if the message is valid.
|
||||
*/
|
||||
private boolean isValid(IMessage message)
|
||||
{
|
||||
return mIgnoreCrcChecksums || message.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary message processing
|
||||
*/
|
||||
@@ -128,7 +143,7 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
mSuperFrameProcessor2.process(voiceMessage);
|
||||
}
|
||||
}
|
||||
else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid())
|
||||
else if(message instanceof DMRBurst dmrBurst && isValid(dmrBurst))
|
||||
{
|
||||
if(dmrBurst.getTimeslot() == 1)
|
||||
{
|
||||
@@ -140,28 +155,15 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
}
|
||||
|
||||
//Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings
|
||||
if(message instanceof ITimeslotFrequencyReceiver)
|
||||
//Process data messages carrying a link control payload so that the LC payload can be processed/enriched
|
||||
if(message instanceof DataMessageWithLinkControl linkControlCarrier)
|
||||
{
|
||||
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
||||
int[] lcns = receiver.getLogicalChannelNumbers();
|
||||
|
||||
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
||||
|
||||
for(int lcn: lcns)
|
||||
{
|
||||
if(mTimeslotFrequencyMap.containsKey(lcn))
|
||||
{
|
||||
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lcn));
|
||||
}
|
||||
}
|
||||
|
||||
if(!timeslotFrequencies.isEmpty())
|
||||
{
|
||||
receiver.apply(timeslotFrequencies);
|
||||
}
|
||||
enrich(linkControlCarrier.getLCMessage());
|
||||
}
|
||||
|
||||
//Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings
|
||||
enrich(message);
|
||||
|
||||
//Now that the message has been (potentially) enriched, dispatch it to the modules
|
||||
dispatch(message);
|
||||
|
||||
@@ -243,19 +245,47 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
|
||||
//Reset talker alias assembler on Idle or Terminator
|
||||
if(message.isValid() && (message instanceof IDLEMessage || message instanceof Terminator))
|
||||
if(isValid(message) && (message instanceof IDLEMessage || message instanceof Terminator))
|
||||
{
|
||||
mTalkerAliasAssembler.reset(message.getTimeslot());
|
||||
}
|
||||
}
|
||||
|
||||
//Assemble Talker Alias from FLC message fragments (header & blocks 1-3)
|
||||
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && message.isValid())
|
||||
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && isValid(message))
|
||||
{
|
||||
dispatch(mTalkerAliasAssembler.process(flc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings
|
||||
* @param message to be enriched
|
||||
*/
|
||||
private void enrich(IMessage message)
|
||||
{
|
||||
if(message instanceof ITimeslotFrequencyReceiver)
|
||||
{
|
||||
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
|
||||
int[] lcns = receiver.getLogicalChannelNumbers();
|
||||
|
||||
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
|
||||
|
||||
for(int lcn: lcns)
|
||||
{
|
||||
if(mTimeslotFrequencyMap.containsKey(lcn))
|
||||
{
|
||||
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lcn));
|
||||
}
|
||||
}
|
||||
|
||||
if(!timeslotFrequencies.isEmpty())
|
||||
{
|
||||
receiver.apply(timeslotFrequencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the non-null message to the registered listener
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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,6 +25,7 @@ import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRNetwork;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRSite;
|
||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||
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.data.csbk.hytera.HyteraAdjacentSiteInformation;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
|
||||
@@ -36,6 +37,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcem
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusControlChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusTrafficChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ControlChannelSystemParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.TrafficChannelSystemParameters;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.Model;
|
||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||
import java.util.ArrayList;
|
||||
@@ -59,6 +62,11 @@ public class DMRNetworkConfigurationMonitor
|
||||
private static final String BRAND_TIER_3_TRUNKING = "Tier III Trunking";
|
||||
private static final String BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING = "Capacity Max Tier III Trunking";
|
||||
private static final String BRAND_HYTERA_TIER_3_TRUNKING = "Hytera Tier III Trunking";
|
||||
private static final String MODE_CAPACITY_MAX_OPEN_SYSTEM = "Open System";
|
||||
private static final String MODE_CAPACITY_MAX_ADVANTAGE = "Advantage";
|
||||
private static final String CHANNEL_TYPE_CONTROL = "Control";
|
||||
private static final String CHANNEL_TYPE_TRAFFIC = "Traffic";
|
||||
private static final String UNKNOWN = "Unknown";
|
||||
|
||||
private List<SiteIdentifier> mNeighborSites = new ArrayList<>();
|
||||
private Map<Integer,AdjacentSiteInformation> mTier3NeighborSites = new HashMap<>();
|
||||
@@ -67,7 +75,10 @@ public class DMRNetworkConfigurationMonitor
|
||||
private DMRSite mDMRSite;
|
||||
private Model mTier3Model;
|
||||
private String mBrand;
|
||||
private Integer mColorCode;
|
||||
private String mMode;
|
||||
private String mChannelType;
|
||||
private Integer mColorCodeTS1;
|
||||
private Integer mColorCodeTS2;
|
||||
private DMRChannel mCurrentChannel;
|
||||
private Channel mChannel;
|
||||
|
||||
@@ -94,13 +105,34 @@ public class DMRNetworkConfigurationMonitor
|
||||
*/
|
||||
public void process(DMRMessage message)
|
||||
{
|
||||
if(message instanceof CSBKMessage)
|
||||
if(message instanceof CSBKMessage csbk)
|
||||
{
|
||||
process((CSBKMessage)message);
|
||||
process(csbk);
|
||||
}
|
||||
else if(message instanceof LCMessage)
|
||||
else if(message instanceof LCMessage lc)
|
||||
{
|
||||
process((LCMessage)message);
|
||||
process(lc);
|
||||
}
|
||||
|
||||
if(message instanceof DataMessage dm)
|
||||
{
|
||||
process(dm);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes data messages to capture the color code for each timeslot.
|
||||
* @param dm data message
|
||||
*/
|
||||
public void process(DataMessage dm)
|
||||
{
|
||||
if(dm.getTimeslot() == 1)
|
||||
{
|
||||
mColorCodeTS1 = dm.getSlotType().getColorCode();
|
||||
}
|
||||
else if(dm.getTimeslot() == 2)
|
||||
{
|
||||
mColorCodeTS2 = dm.getSlotType().getColorCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,30 +143,59 @@ public class DMRNetworkConfigurationMonitor
|
||||
{
|
||||
switch(linkControl.getOpcode())
|
||||
{
|
||||
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS:
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
|
||||
mBrand = BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING;
|
||||
break;
|
||||
|
||||
case SHORT_CONNECT_PLUS_CONTROL_CHANNEL:
|
||||
if((mDMRNetwork == null || mDMRSite == null) && linkControl instanceof ConnectPlusControlChannel)
|
||||
if(linkControl instanceof ConnectPlusControlChannel cpcc)
|
||||
{
|
||||
ConnectPlusControlChannel cpcc = (ConnectPlusControlChannel)linkControl;
|
||||
mDMRNetwork = cpcc.getNetwork();
|
||||
mDMRSite = cpcc.getSite();
|
||||
}
|
||||
if(mBrand == null)
|
||||
{
|
||||
mChannelType = CHANNEL_TYPE_CONTROL;
|
||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||
}
|
||||
break;
|
||||
case SHORT_CONNECT_PLUS_TRAFFIC_CHANNEL:
|
||||
if((mDMRNetwork == null || mDMRSite == null) && linkControl instanceof ConnectPlusTrafficChannel)
|
||||
if(linkControl instanceof ConnectPlusTrafficChannel cptc)
|
||||
{
|
||||
ConnectPlusTrafficChannel cptc = (ConnectPlusTrafficChannel)linkControl;
|
||||
mDMRNetwork = cptc.getNetwork();
|
||||
mDMRSite = cptc.getSite();
|
||||
}
|
||||
if(mBrand == null)
|
||||
{
|
||||
mChannelType = CHANNEL_TYPE_TRAFFIC;
|
||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||
}
|
||||
break;
|
||||
case SHORT_STANDARD_CONTROL_CHANNEL_SYSTEM_PARAMETERS:
|
||||
if(linkControl instanceof ControlChannelSystemParameters cc)
|
||||
{
|
||||
SystemIdentityCode sic = cc.getSystemIdentityCode();
|
||||
mTier3Model = sic.getModel();
|
||||
mDMRNetwork = sic.getNetwork();
|
||||
mDMRSite = sic.getSite();
|
||||
mChannelType = CHANNEL_TYPE_CONTROL;
|
||||
|
||||
if(mBrand == null)
|
||||
{
|
||||
mBrand = BRAND_TIER_3_TRUNKING;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SHORT_STANDARD_TRAFFIC_CHANNEL_SYSTEM_PARAMETERS:
|
||||
if(linkControl instanceof TrafficChannelSystemParameters tc)
|
||||
{
|
||||
SystemIdentityCode sic = tc.getSystemIdentityCode();
|
||||
mTier3Model = sic.getModel();
|
||||
mDMRNetwork = sic.getNetwork();
|
||||
mDMRSite = sic.getSite();
|
||||
mChannelType = CHANNEL_TYPE_TRAFFIC;
|
||||
}
|
||||
|
||||
if(mBrand == null)
|
||||
{
|
||||
mBrand = BRAND_TIER_3_TRUNKING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +233,12 @@ public class DMRNetworkConfigurationMonitor
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STANDARD_ANNOUNCEMENT:
|
||||
if(csbk instanceof AdjacentSiteInformation neighbor)
|
||||
{
|
||||
mTier3NeighborSites.put(neighbor.getNeighborSystemIdentityCode().getSite().getValue(), neighbor);
|
||||
}
|
||||
break;
|
||||
case HYTERA_08_ANNOUNCEMENT:
|
||||
case HYTERA_68_ANNOUNCEMENT:
|
||||
if(csbk instanceof HyteraAnnouncement)
|
||||
@@ -196,10 +263,7 @@ public class DMRNetworkConfigurationMonitor
|
||||
mTier3Model = ha.getSystemIdentityCode().getModel();
|
||||
}
|
||||
|
||||
if(mBrand == null || mBrand != BRAND_HYTERA_TIER_3_TRUNKING)
|
||||
{
|
||||
mBrand = BRAND_HYTERA_TIER_3_TRUNKING;
|
||||
}
|
||||
mBrand = BRAND_HYTERA_TIER_3_TRUNKING;
|
||||
}
|
||||
if(csbk instanceof HyteraAdjacentSiteInformation)
|
||||
{
|
||||
@@ -234,22 +298,25 @@ public class DMRNetworkConfigurationMonitor
|
||||
}
|
||||
}
|
||||
|
||||
if(mBrand == null || mBrand != BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING)
|
||||
{
|
||||
mBrand = BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING;
|
||||
}
|
||||
mChannelType = CHANNEL_TYPE_CONTROL;
|
||||
mBrand = BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING;
|
||||
}
|
||||
break;
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE:
|
||||
mBrand = BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING;
|
||||
mMode = MODE_CAPACITY_MAX_ADVANTAGE;
|
||||
break;
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||
mBrand = BRAND_MOTOROLA_CAPACITY_MAX_TIER_3_TRUNKING;
|
||||
mMode = MODE_CAPACITY_MAX_OPEN_SYSTEM;
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_NEIGHBOR_REPORT:
|
||||
if(mNeighborSites.isEmpty() && csbk instanceof ConnectPlusNeighborReport)
|
||||
{
|
||||
ConnectPlusNeighborReport cpnr = (ConnectPlusNeighborReport)csbk;
|
||||
mNeighborSites.addAll(cpnr.getNeighbors());
|
||||
}
|
||||
if(mBrand == null || mBrand != BRAND_MOTOROLA_CONNECT_PLUS)
|
||||
{
|
||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||
}
|
||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_VOICE_CHANNEL_USER:
|
||||
if(csbk instanceof ConnectPlusVoiceChannelUser)
|
||||
@@ -257,17 +324,9 @@ public class DMRNetworkConfigurationMonitor
|
||||
DMRChannel channel = ((ConnectPlusVoiceChannelUser)csbk).getChannel();
|
||||
addDmrChannel(channel);
|
||||
}
|
||||
if(mBrand == null)
|
||||
{
|
||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||
}
|
||||
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
|
||||
break;
|
||||
}
|
||||
|
||||
if(mColorCode == null)
|
||||
{
|
||||
mColorCode = csbk.getSlotType().getColorCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,33 +337,31 @@ public class DMRNetworkConfigurationMonitor
|
||||
mObservedChannelMap.put(dmrChannel.getValue(), dmrChannel);
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
mColorCode = null;
|
||||
mDMRNetwork = null;
|
||||
mDMRSite = null;
|
||||
mNeighborSites.clear();
|
||||
mTier3NeighborSites.clear();
|
||||
mObservedChannelMap.clear();
|
||||
}
|
||||
|
||||
public String getActivitySummary()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Activity Summary - Decoder: DMR ");
|
||||
|
||||
//DMR System Brand Name
|
||||
sb.append("\n\nBrand:").append((mBrand == null ? BRAND_STANDARD : mBrand));
|
||||
sb.append("\n\nBrand: ").append((mBrand == null ? BRAND_STANDARD : mBrand));
|
||||
|
||||
if(mMode != null)
|
||||
{
|
||||
sb.append("\nConfigured Mode: ").append(mMode);
|
||||
}
|
||||
|
||||
sb.append("\nChannel Type: ").append((mChannelType != null) ? mChannelType : UNKNOWN);
|
||||
sb.append("\nColor Code Timeslot 1: ").append(mColorCodeTS1 != null ? mColorCodeTS1 : UNKNOWN);
|
||||
sb.append("\nColor Code Timeslot 2: ").append(mColorCodeTS2 != null ? mColorCodeTS2 : UNKNOWN);
|
||||
|
||||
if(mDMRNetwork != null)
|
||||
{
|
||||
sb.append("\nNetwork:").append(mDMRNetwork);
|
||||
sb.append("\nNetwork: ").append(mDMRNetwork);
|
||||
}
|
||||
|
||||
if(mDMRSite != null)
|
||||
{
|
||||
sb.append("\nSite:").append(mDMRSite);
|
||||
sb.append("\nSite: ").append(mDMRSite);
|
||||
}
|
||||
|
||||
if(mTier3Model != null)
|
||||
@@ -347,6 +404,11 @@ public class DMRNetworkConfigurationMonitor
|
||||
{
|
||||
sb.append("\tNeighbor: ").append(neighbor).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if(!mTier3NeighborSites.isEmpty())
|
||||
{
|
||||
sb.append("\nTier III Neighbor Sites\n");
|
||||
|
||||
for(Map.Entry<Integer,AdjacentSiteInformation> neighbor: mTier3NeighborSites.entrySet())
|
||||
{
|
||||
@@ -354,7 +416,16 @@ public class DMRNetworkConfigurationMonitor
|
||||
sb.append("\tNeighbor: ").append("Network:").append(site.getNeighborSystemIdentityCode().getNetwork());
|
||||
sb.append(" Site:").append(site.getNeighborSystemIdentityCode().getSite());
|
||||
sb.append(" ").append(site.getNeighborSystemIdentityCode().getModel());
|
||||
sb.append(" ").append(site.getNeighborChannel()).append("\n");
|
||||
|
||||
DMRChannel channel = site.getNeighborChannel();
|
||||
sb.append(" ").append(channel);
|
||||
|
||||
if(channel.getDownlinkFrequency() > 0)
|
||||
{
|
||||
sb.append(" ").append(channel.getDownlinkFrequency());
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -37,7 +37,7 @@ import io.github.dsheirer.message.MessageHistoryRequest;
|
||||
import io.github.dsheirer.message.MessageHistoryResponse;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
|
||||
import io.github.dsheirer.module.decode.dmr.event.DMRChannelGrantEvent;
|
||||
import io.github.dsheirer.module.decode.dmr.event.DMRDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.Opcode;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
@@ -63,6 +63,7 @@ import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -83,36 +84,32 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* This manager monitors channel events watching for events related to managed traffic channels.
|
||||
*/
|
||||
public class DMRTrafficChannelManager extends TrafficChannelManager implements IDecodeEventProvider, IChannelEventListener,
|
||||
IChannelEventProvider
|
||||
public class DMRTrafficChannelManager extends TrafficChannelManager implements IChannelEventListener,
|
||||
IChannelEventProvider, IDecodeEventProvider
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRTrafficChannelManager.class);
|
||||
public static final String CHANNEL_START_REJECTED = " REJECTED - NO TUNER";
|
||||
public static final String DATA_CALL_IGNORED = "DATA CALL IGNORED";
|
||||
public static final String IGNORED = " - Ignored";
|
||||
public static final String MAX_TRAFFIC_CHANNELS_EXCEEDED = "MAX TRAFFIC CHANNELS EXCEEDED";
|
||||
public static final String NO_FREQUENCY = "NO FREQUENCY - CHECK LSN CHANNEL MAP";
|
||||
public static final String NO_FREQUENCY = "NO FREQUENCY - CHECK PLAYLIST CHANNEL CONFIG LSN CHANNEL MAP";
|
||||
public static final long EVENT_TIME_STALE_THRESHOLD = 5000; //5 seconds
|
||||
|
||||
private Queue<Channel> mAvailableTrafficChannelQueue = new ConcurrentLinkedQueue<>();
|
||||
private List<Channel> mManagedTrafficChannels;
|
||||
|
||||
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
|
||||
private Map<DMRChannel,DMRChannelGrantEvent> mChannelGrantEventMap = new ConcurrentHashMap<>();
|
||||
|
||||
private Queue<Channel> mAvailableTrafficChannels = new ConcurrentLinkedQueue<>();
|
||||
private List<Channel> mAllocatedTrafficChannels;
|
||||
private Map<Long,Channel> mAllocatedChannelFrequencyMap = new ConcurrentHashMap<>();
|
||||
private ReentrantLock mLock = new ReentrantLock();
|
||||
private Map<Long,IDecodeEvent> mCallEventsTS1 = new ConcurrentHashMap<>();
|
||||
private Map<Long,IDecodeEvent> mCallEventsTS2 = new ConcurrentHashMap<>();
|
||||
private Listener<ChannelEvent> mChannelEventListener;
|
||||
private Listener<IDecodeEvent> mDecodeEventListener;
|
||||
|
||||
private TrafficChannelTeardownMonitor mTrafficChannelTeardownMonitor = new TrafficChannelTeardownMonitor();
|
||||
private Channel mParentChannel;
|
||||
private boolean mIgnoreDataCalls;
|
||||
|
||||
//Used as temporary storage for message and decode event history during Cap+ REST channel rotation
|
||||
private DecodeEventHistory mTransientDecodeEventHistory;
|
||||
private List<IMessage> mTransientMessageHistory;
|
||||
|
||||
private boolean mIgnoreDataCalls;
|
||||
private long mCurrentControlFrequency;
|
||||
|
||||
/**
|
||||
* Monitors call events and allocates traffic decoder channels in response
|
||||
* to traffic channel allocation call events. Manages a pool of reusable
|
||||
@@ -135,11 +132,38 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
/**
|
||||
* Sets the current parent control channel frequency so that channel grants for the current frequency do not
|
||||
* produce an additional traffic channel allocation.
|
||||
* @param currentControlFrequency for current control channel.
|
||||
* @param previousControlFrequency for the current control channel (to remove from allocated channels)
|
||||
* @param currentControlFrequency for current control channel (to add to allocated channels)
|
||||
* @param channel for the current control channel
|
||||
*/
|
||||
public void setCurrentControlFrequency(long currentControlFrequency)
|
||||
public void setCurrentControlFrequency(long previousControlFrequency, long currentControlFrequency, Channel channel)
|
||||
{
|
||||
mCurrentControlFrequency = currentControlFrequency;
|
||||
if(previousControlFrequency == currentControlFrequency)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
Channel existing = mAllocatedChannelFrequencyMap.get(previousControlFrequency);
|
||||
|
||||
//Only remove the channel if it is non-null and it matches the current control channel.
|
||||
if(channel.equals(existing))
|
||||
{
|
||||
//Unlock the frequency in the channel rotation monitor
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(previousControlFrequency));
|
||||
mAllocatedChannelFrequencyMap.remove(previousControlFrequency);
|
||||
}
|
||||
|
||||
mAllocatedChannelFrequencyMap.put(currentControlFrequency, channel);
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(currentControlFrequency));
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +173,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
*/
|
||||
private void createTrafficChannels()
|
||||
{
|
||||
if(mManagedTrafficChannels == null)
|
||||
if(mAllocatedTrafficChannels == null)
|
||||
{
|
||||
DecodeConfiguration decodeConfiguration = mParentChannel.getDecodeConfiguration();
|
||||
List<Channel> trafficChannelList = new ArrayList<>();
|
||||
@@ -176,8 +200,11 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
}
|
||||
}
|
||||
|
||||
mAvailableTrafficChannelQueue.addAll(trafficChannelList);
|
||||
mManagedTrafficChannels = Collections.unmodifiableList(trafficChannelList);
|
||||
mAvailableTrafficChannels.addAll(trafficChannelList);
|
||||
|
||||
//Keep track of the complete list so that we can check channel events to determine if the channel event
|
||||
//is for a traffic channel owned by this traffic channel manager.
|
||||
mAllocatedTrafficChannels = Collections.unmodifiableList(trafficChannelList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,85 +218,93 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
public void convertToTrafficChannel(Channel channel, long currentFrequency, IChannelDescriptor restChannel,
|
||||
DMRNetworkConfigurationMonitor networkConfigurationMonitor)
|
||||
{
|
||||
//Only do the conversion of the original channel has multiple frequencies defined and the rest channel is
|
||||
//one of those frequencies
|
||||
if(restChannel.getDownlinkFrequency() > 0 &&
|
||||
!mAllocatedTrafficChannelFrequencyMap.containsKey(restChannel.getDownlinkFrequency()) &&
|
||||
channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
SourceConfigTunerMultipleFrequency originalSourceConfig = (SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration();
|
||||
long rest = restChannel.getDownlinkFrequency();
|
||||
|
||||
//Add the rest channel to the list of frequencies in the source configuration
|
||||
if(!originalSourceConfig.getFrequencies().contains(restChannel.getDownlinkFrequency()))
|
||||
//Only do the conversion of the original channel has multiple frequencies defined and the rest channel is
|
||||
//one of those frequencies
|
||||
if(rest > 0 && !mAllocatedChannelFrequencyMap.containsKey(rest) &&
|
||||
channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
||||
{
|
||||
originalSourceConfig.addFrequency(restChannel.getDownlinkFrequency());
|
||||
SourceConfigTunerMultipleFrequency originalSourceConfig = (SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration();
|
||||
|
||||
//Note: the channel configuration modification is not persisted from here, to avoid unnecessary coupling
|
||||
//to the channel model. The configuration will simply be adjusted at runtime.
|
||||
}
|
||||
|
||||
//Convert current channel to a traffic channel if one is available
|
||||
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
|
||||
|
||||
if(trafficChannel != null && restChannel.getDownlinkFrequency() > 0)
|
||||
{
|
||||
//Disable the channel rotation manager when we have multiple frequencies defined
|
||||
getInterModuleEventBus().post(new DisableChannelRotationMonitorRequest());
|
||||
|
||||
//Set the frequency for the traffic channel configuration
|
||||
SourceConfigTuner trafficSourceConfig = new SourceConfigTuner();
|
||||
trafficSourceConfig.setFrequency(currentFrequency);
|
||||
trafficChannel.setSourceConfiguration(trafficSourceConfig);
|
||||
|
||||
//Post a request for message and decode event history to transfer to the new REST channel. This has
|
||||
// to be posted before the channel conversion request
|
||||
getInterModuleEventBus().post(new DecodeEventHistoryRequest());
|
||||
getInterModuleEventBus().post(new MessageHistoryRequest());
|
||||
|
||||
//Dispatch a request to convert this processing chain to the traffic channel. This will cause the
|
||||
//processing chain to convert to a traffic channel and notify all of the modules, which will in-turn
|
||||
//cause the decoder states (2 timeslots) to dereference this manager so that the existing channel can
|
||||
//no longer allocate traffic channels.
|
||||
getInterModuleEventBus().post(new ChannelConversionRequest(channel, trafficChannel));
|
||||
|
||||
mAllocatedTrafficChannelFrequencyMap.put(currentFrequency, trafficChannel);
|
||||
|
||||
//Set the preferred frequency to use when restarting the original channel
|
||||
originalSourceConfig.setPreferredFrequency(restChannel.getDownlinkFrequency());
|
||||
|
||||
//Dispatch request to persistently start the original channel with the rest channel frequency and reuse
|
||||
//this traffic channel manager in the new processing chain.
|
||||
ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, this);
|
||||
request.setPersistentAttempt(true);
|
||||
request.setChildDecodeEventHistory(mTransientDecodeEventHistory);
|
||||
|
||||
//If we received an event history response, add it to the request as preload data content
|
||||
if(mTransientDecodeEventHistory != null)
|
||||
{
|
||||
DecodeEventHistoryPreloadData eventHistory =
|
||||
new DecodeEventHistoryPreloadData(mTransientDecodeEventHistory.getItems());
|
||||
request.addPreloadDataContent(eventHistory);
|
||||
|
||||
mTransientDecodeEventHistory = null;
|
||||
}
|
||||
|
||||
//If we received a message history response, add it to the request as preload data content
|
||||
if(mTransientMessageHistory != null)
|
||||
{
|
||||
MessageHistoryPreloadData messageHistory = new MessageHistoryPreloadData(mTransientMessageHistory);
|
||||
request.addPreloadDataContent(messageHistory);
|
||||
mTransientMessageHistory = null;
|
||||
}
|
||||
|
||||
//Add the DMR network configuration monitor as preload data
|
||||
if(networkConfigurationMonitor != null)
|
||||
{
|
||||
request.addPreloadDataContent(new DMRNetworkConfigurationPreloadData(networkConfigurationMonitor));
|
||||
}
|
||||
|
||||
getInterModuleEventBus().post(request);
|
||||
//Add the rest channel to the list of frequencies in the source configuration
|
||||
if(!originalSourceConfig.getFrequencies().contains(restChannel.getDownlinkFrequency()))
|
||||
{
|
||||
originalSourceConfig.addFrequency(restChannel.getDownlinkFrequency());
|
||||
}
|
||||
|
||||
//Convert current channel to a traffic channel if one is available
|
||||
Channel trafficChannel = mAvailableTrafficChannels.poll();
|
||||
|
||||
if(trafficChannel != null)
|
||||
{
|
||||
//Disable the channel rotation manager when we have multiple frequencies defined
|
||||
getInterModuleEventBus().post(new DisableChannelRotationMonitorRequest());
|
||||
|
||||
//Set the frequency for the traffic channel configuration
|
||||
SourceConfigTuner trafficSourceConfig = new SourceConfigTuner();
|
||||
trafficSourceConfig.setFrequency(currentFrequency);
|
||||
trafficChannel.setSourceConfiguration(trafficSourceConfig);
|
||||
|
||||
//Post a request for message and decode event history to transfer to the new REST channel. This has
|
||||
// to be posted before the channel conversion request
|
||||
getInterModuleEventBus().post(new DecodeEventHistoryRequest());
|
||||
getInterModuleEventBus().post(new MessageHistoryRequest());
|
||||
|
||||
//Dispatch a request to convert this processing chain to the traffic channel. This will cause the
|
||||
//processing chain to convert to a traffic channel and notify all of the modules, which will in-turn
|
||||
//cause the decoder states (2 timeslots) to dereference this manager so that the existing channel can
|
||||
//no longer allocate traffic channels.
|
||||
getInterModuleEventBus().post(new ChannelConversionRequest(channel, trafficChannel));
|
||||
|
||||
mAllocatedChannelFrequencyMap.put(currentFrequency, trafficChannel);
|
||||
|
||||
//Set the preferred frequency to use when restarting the original channel
|
||||
originalSourceConfig.setPreferredFrequency(rest);
|
||||
|
||||
//Dispatch request to persistently start the original channel with the rest channel frequency and reuse
|
||||
//this traffic channel manager in the new processing chain.
|
||||
ChannelStartProcessingRequest request = new ChannelStartProcessingRequest(channel, restChannel,
|
||||
null, this);
|
||||
request.setPersistentAttempt(true);
|
||||
request.setChildDecodeEventHistory(mTransientDecodeEventHistory);
|
||||
|
||||
//If we received an event history response, add it to the request as preload data content
|
||||
if(mTransientDecodeEventHistory != null)
|
||||
{
|
||||
DecodeEventHistoryPreloadData eventHistory =
|
||||
new DecodeEventHistoryPreloadData(mTransientDecodeEventHistory.getItems());
|
||||
request.addPreloadDataContent(eventHistory);
|
||||
|
||||
mTransientDecodeEventHistory = null;
|
||||
}
|
||||
|
||||
//If we received a message history response, add it to the request as preload data content
|
||||
if(mTransientMessageHistory != null)
|
||||
{
|
||||
MessageHistoryPreloadData messageHistory = new MessageHistoryPreloadData(mTransientMessageHistory);
|
||||
request.addPreloadDataContent(messageHistory);
|
||||
mTransientMessageHistory = null;
|
||||
}
|
||||
|
||||
//Add the DMR network configuration monitor as preload data
|
||||
if(networkConfigurationMonitor != null)
|
||||
{
|
||||
request.addPreloadDataContent(new DMRNetworkConfigurationPreloadData(networkConfigurationMonitor));
|
||||
}
|
||||
|
||||
getInterModuleEventBus().post(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,7 +336,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
/**
|
||||
* Broadcasts an initial or update decode event to any registered listener.
|
||||
*/
|
||||
public void broadcast(DecodeEvent decodeEvent)
|
||||
public void broadcast(IDecodeEvent decodeEvent)
|
||||
{
|
||||
if(mDecodeEventListener != null)
|
||||
{
|
||||
@@ -309,119 +344,159 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
}
|
||||
}
|
||||
|
||||
public void processEndChannelGrant()
|
||||
/**
|
||||
* Receive decode events/updates from traffic channels to capture in the parent channel's decode event history. We
|
||||
* do this by rebroadcasting the event to the parent control channel's processing chain so that the event history
|
||||
* can receive the event or update.
|
||||
* @param trafficChannelEvent to receive and rebroadcast.
|
||||
*/
|
||||
public void receiveTrafficChannelEvent(IDecodeEvent trafficChannelEvent)
|
||||
{
|
||||
//TODO: process the CSBK opcode that indicates traffic channel teardown
|
||||
broadcast(trafficChannelEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes channel grants to allocate traffic channels and track overall channel usage. Generates
|
||||
* decode events for each new channel that is allocated.
|
||||
*
|
||||
*/
|
||||
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
|
||||
Opcode opcode, long timestamp, boolean encrypted)
|
||||
{
|
||||
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||
mLock.lock();
|
||||
|
||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||
try
|
||||
{
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
||||
.channel(channel)
|
||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
boolean allocated = mAllocatedChannelFrequencyMap.containsKey(channel.getDownlinkFrequency());
|
||||
|
||||
mChannelGrantEventMap.put(channel, event);
|
||||
|
||||
}
|
||||
else //Update current event
|
||||
{
|
||||
Identifier from = getIdentifier(identifierCollection, Role.FROM);
|
||||
|
||||
if(from != null)
|
||||
if(allocated)
|
||||
{
|
||||
Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM);
|
||||
if(currentFrom != null && !Objects.equals(from, currentFrom))
|
||||
{
|
||||
event.end(timestamp);
|
||||
|
||||
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
|
||||
.channel(channel)
|
||||
.details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.build();
|
||||
|
||||
mChannelGrantEventMap.put(channel, event);
|
||||
broadcast(event);
|
||||
}
|
||||
}
|
||||
|
||||
//update the ending timestamp so that the duration value is correctly calculated
|
||||
event.update(timestamp);
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
|
||||
//Even though we have an event, the initial or continuation channel grant may have been rejected. Check to see
|
||||
// if there is a traffic channel allocated. If not, allocate one and update the event description.
|
||||
long frequency = channel.getDownlinkFrequency();
|
||||
|
||||
if(frequency == 0)
|
||||
{
|
||||
if(event.getDetails() == null)
|
||||
{
|
||||
event.setDetails(NO_FREQUENCY);
|
||||
}
|
||||
else if(!event.getDetails().endsWith(NO_FREQUENCY))
|
||||
{
|
||||
event.setDetails(event.getDetails() + " - " + NO_FREQUENCY);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(frequency != mCurrentControlFrequency && !mAllocatedTrafficChannelFrequencyMap.containsKey(frequency))
|
||||
{
|
||||
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
|
||||
{
|
||||
if(event.getDetails() == null)
|
||||
{
|
||||
event.setDetails(DATA_CALL_IGNORED);
|
||||
}
|
||||
else if(!event.getDetails().endsWith(DATA_CALL_IGNORED))
|
||||
{
|
||||
event.setDetails(event.getDetails() + " - " + DATA_CALL_IGNORED);
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
return;
|
||||
}
|
||||
|
||||
Channel trafficChannel = mAvailableTrafficChannelQueue.poll();
|
||||
|
||||
if(trafficChannel != null)
|
||||
{
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(frequency);
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedTrafficChannelFrequencyMap.put(frequency, trafficChannel);
|
||||
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel,
|
||||
identifierCollection));
|
||||
//Note: if traffic channel is allocated then do nothing & let the channel maintain its own event state
|
||||
}
|
||||
else
|
||||
{
|
||||
if(event.getDetails() == null)
|
||||
long frequency = channel.getDownlinkFrequency();
|
||||
|
||||
//If we don't have a frequency value for the channel, use the channel number as a place holder. We won't
|
||||
//allocate a traffic channel for it, but we can track the related channel grant event.
|
||||
if(frequency == 0)
|
||||
{
|
||||
event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
|
||||
frequency = channel.getChannelNumber();
|
||||
}
|
||||
else if(!event.getDetails().endsWith(MAX_TRAFFIC_CHANNELS_EXCEEDED))
|
||||
|
||||
Map<Long,IDecodeEvent> eventMap = channel.getTimeslot() == 1 ? mCallEventsTS1 : mCallEventsTS2;
|
||||
IDecodeEvent temp = eventMap.get(frequency);
|
||||
DecodeEvent event = (temp != null) ? (DecodeEvent)temp : null;
|
||||
|
||||
if(isStale(event, timestamp, identifierCollection)) //Create new event
|
||||
{
|
||||
event.setDetails(event.getDetails() + " - " + MAX_TRAFFIC_CHANNELS_EXCEEDED);
|
||||
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||
event = DMRDecodeEvent.builder(decodeEventType, timestamp)
|
||||
.channel(channel)
|
||||
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
eventMap.put(frequency, event);
|
||||
}
|
||||
else if(event != null) //Update current event
|
||||
{
|
||||
Identifier from = getIdentifier(identifierCollection, Role.FROM);
|
||||
|
||||
if(from != null)
|
||||
{
|
||||
Identifier currentFrom = getIdentifier(event.getIdentifierCollection(), Role.FROM);
|
||||
if(currentFrom != null && !Objects.equals(from, currentFrom))
|
||||
{
|
||||
event.end(timestamp);
|
||||
|
||||
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
|
||||
event = DMRDecodeEvent.builder(decodeEventType, timestamp)
|
||||
.channel(channel)
|
||||
.details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
|
||||
.identifiers(identifierCollection)
|
||||
.timeslot(channel.getTimeslot())
|
||||
.build();
|
||||
|
||||
eventMap.put(frequency, event);
|
||||
}
|
||||
}
|
||||
|
||||
//update the ending timestamp so that the duration value is correctly calculated
|
||||
event.update(timestamp);
|
||||
}
|
||||
|
||||
//If our channel doesn't have a frequency value, update the event to reflect this.
|
||||
if(channel.getDownlinkFrequency() == 0)
|
||||
{
|
||||
if(event.getDetails() == null)
|
||||
{
|
||||
event.setDetails(NO_FREQUENCY);
|
||||
}
|
||||
else if(!event.getDetails().endsWith(NO_FREQUENCY))
|
||||
{
|
||||
event.setDetails(event.getDetails() + " - " + NO_FREQUENCY);
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
|
||||
{
|
||||
if(event.getDetails() == null)
|
||||
{
|
||||
event.setDetails(DATA_CALL_IGNORED);
|
||||
}
|
||||
else if(!event.getDetails().endsWith(DATA_CALL_IGNORED))
|
||||
{
|
||||
event.setDetails(event.getDetails() + " - " + DATA_CALL_IGNORED);
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
return;
|
||||
}
|
||||
|
||||
Channel trafficChannel = mAvailableTrafficChannels.poll();
|
||||
|
||||
if(trafficChannel != null)
|
||||
{
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(frequency);
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedChannelFrequencyMap.put(frequency, trafficChannel);
|
||||
//Preload the channel grant event for the traffic channel to use/maintain
|
||||
if(trafficChannel.getDecodeConfiguration() instanceof DecodeConfigDMR config)
|
||||
{
|
||||
config.setChannelGrantEvent(event);
|
||||
}
|
||||
|
||||
getInterModuleEventBus().post(new ChannelStartProcessingRequest(trafficChannel, channel,
|
||||
identifierCollection, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(event.getDetails() == null)
|
||||
{
|
||||
event.setDetails(MAX_TRAFFIC_CHANNELS_EXCEEDED);
|
||||
}
|
||||
else if(!event.getDetails().endsWith(MAX_TRAFFIC_CHANNELS_EXCEEDED))
|
||||
{
|
||||
event.setDetails(event.getDetails() + " - " + MAX_TRAFFIC_CHANNELS_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//This shouldn't happen, but we'll log if it ever does.
|
||||
mLog.error("Error while processing channel grant event - traffic channel lock is released", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -436,6 +511,8 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
{
|
||||
case STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
case STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT:
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE:
|
||||
type = encrypted ? DecodeEventType.CALL_GROUP_ENCRYPTED : DecodeEventType.CALL_GROUP;
|
||||
break;
|
||||
|
||||
@@ -540,7 +617,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
* @param currentIdentifiers to compare against the event
|
||||
* @return true if the event is stale.
|
||||
*/
|
||||
private boolean isStale(DecodeEvent event, long timestamp, IdentifierCollection currentIdentifiers)
|
||||
private boolean isStale(IDecodeEvent event, long timestamp, IdentifierCollection currentIdentifiers)
|
||||
{
|
||||
if(event == null)
|
||||
{
|
||||
@@ -609,23 +686,41 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
for(Long frequency: mAllocatedTrafficChannelFrequencyMap.keySet())
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(frequency));
|
||||
for(Long frequency: mAllocatedChannelFrequencyMap.keySet())
|
||||
{
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.lock(frequency));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
mAvailableTrafficChannelQueue.clear();
|
||||
mLock.lock();
|
||||
|
||||
List<Channel> channels = new ArrayList<>(mAllocatedTrafficChannelFrequencyMap.values());
|
||||
|
||||
//Issue a disable request for each traffic channel
|
||||
for(Channel channel: channels)
|
||||
try
|
||||
{
|
||||
broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
|
||||
mAvailableTrafficChannels.clear();
|
||||
|
||||
List<Channel> channels = new ArrayList<>(mAllocatedChannelFrequencyMap.values());
|
||||
|
||||
//Issue a disable request for each traffic channel
|
||||
for(Channel channel: channels)
|
||||
{
|
||||
broadcast(new ChannelEvent(channel, Event.REQUEST_DISABLE));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,115 +736,153 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
|
||||
*/
|
||||
private void removeCallEvents(long frequency)
|
||||
{
|
||||
List<DMRChannel> channelsToRemove = new ArrayList<>();
|
||||
mLock.lock();
|
||||
|
||||
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||
try
|
||||
{
|
||||
if(entry.getKey().getDownlinkFrequency() == frequency)
|
||||
{
|
||||
channelsToRemove.add(entry.getKey());
|
||||
}
|
||||
mCallEventsTS1.remove(frequency);
|
||||
mCallEventsTS2.remove(frequency);
|
||||
}
|
||||
|
||||
for(DMRChannel channel: channelsToRemove)
|
||||
finally
|
||||
{
|
||||
mChannelGrantEventMap.remove(channel);
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCallEventDetails(long frequency, String detailFragment)
|
||||
/**
|
||||
* Updates any call events related to the frequency to indicate the channel start was rejected.
|
||||
* @param frequency for the channel.
|
||||
*/
|
||||
private void setChannelStartRejected(long frequency)
|
||||
{
|
||||
List<DMRChannel> channelsToUpdate = new ArrayList<>();
|
||||
mLock.lock();
|
||||
|
||||
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
|
||||
try
|
||||
{
|
||||
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
|
||||
IDecodeEvent event = mCallEventsTS1.get(frequency);
|
||||
|
||||
if(event instanceof DecodeEvent decodeEvent1)
|
||||
{
|
||||
channelsToUpdate.add(entry.getKey());
|
||||
if(decodeEvent1.getDetails() == null)
|
||||
{
|
||||
decodeEvent1.setDetails(CHANNEL_START_REJECTED);
|
||||
}
|
||||
else if(!decodeEvent1.getDetails().endsWith(CHANNEL_START_REJECTED))
|
||||
{
|
||||
decodeEvent1.setDetails(event.getDetails() + "-" + CHANNEL_START_REJECTED);
|
||||
}
|
||||
|
||||
broadcast(decodeEvent1);
|
||||
}
|
||||
|
||||
event = mCallEventsTS2.get(frequency);
|
||||
|
||||
if(event instanceof DecodeEvent decodeEvent2)
|
||||
{
|
||||
if(decodeEvent2.getDetails() == null)
|
||||
{
|
||||
decodeEvent2.setDetails(CHANNEL_START_REJECTED);
|
||||
}
|
||||
else if(!decodeEvent2.getDetails().endsWith(CHANNEL_START_REJECTED))
|
||||
{
|
||||
decodeEvent2.setDetails(decodeEvent2.getDetails() + "-" + CHANNEL_START_REJECTED);
|
||||
}
|
||||
|
||||
broadcast(decodeEvent2);
|
||||
}
|
||||
}
|
||||
|
||||
for(DMRChannel channel: channelsToUpdate)
|
||||
finally
|
||||
{
|
||||
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
|
||||
|
||||
if(event != null)
|
||||
{
|
||||
if(event.getDetails() == null)
|
||||
{
|
||||
event.setDetails(detailFragment);
|
||||
}
|
||||
else if(!event.getDetails().endsWith(detailFragment))
|
||||
{
|
||||
event.setDetails(event.getDetails() + detailFragment);
|
||||
}
|
||||
|
||||
broadcast(event);
|
||||
}
|
||||
mLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Process channel events from the ChannelProcessingManager to account for owned child traffic channels.
|
||||
* Note: this method sees events for ALL channels and not just DMR channels managed by this instance.
|
||||
*
|
||||
* @param channelEvent to process
|
||||
*/
|
||||
@Override
|
||||
public synchronized void receive(ChannelEvent channelEvent)
|
||||
{
|
||||
Channel channel = channelEvent.getChannel();
|
||||
|
||||
if(channel.isTrafficChannel())
|
||||
//Only process the event if it's one of the traffic channels managed by this instance.
|
||||
if(mAllocatedTrafficChannels.contains(channel))
|
||||
{
|
||||
switch(channelEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_PROCESSING_STOP:
|
||||
long frequencyToRemove = 0;
|
||||
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
|
||||
mLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedChannelFrequencyMap.entrySet())
|
||||
{
|
||||
frequencyToRemove = entry.getKey();
|
||||
removeCallEvents(entry.getKey());
|
||||
break;
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
{
|
||||
frequencyToRemove = entry.getKey();
|
||||
removeCallEvents(entry.getKey());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(frequencyToRemove > 0)
|
||||
{
|
||||
mAllocatedChannelFrequencyMap.remove(frequencyToRemove);
|
||||
|
||||
//Unlock the frequency in the channel rotation monitor
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove));
|
||||
}
|
||||
|
||||
//Add the traffic channel back to the queue to be reused
|
||||
if(!mAvailableTrafficChannels.contains(channel))
|
||||
{
|
||||
mAvailableTrafficChannels.add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
if(frequencyToRemove > 0)
|
||||
finally
|
||||
{
|
||||
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToRemove);
|
||||
|
||||
//Unlock the frequency in the channel rotation monitor
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToRemove));
|
||||
}
|
||||
|
||||
//Add the traffic channel back to the queue to be reused
|
||||
if(!mAvailableTrafficChannelQueue.contains(channel))
|
||||
{
|
||||
mAvailableTrafficChannelQueue.add(channel);
|
||||
mLock.unlock();
|
||||
}
|
||||
break;
|
||||
case NOTIFICATION_PROCESSING_START_REJECTED:
|
||||
long frequencyToUpdate = 0;
|
||||
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedTrafficChannelFrequencyMap.entrySet())
|
||||
mLock.lock();
|
||||
try
|
||||
{
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
long frequencyToUpdate = 0;
|
||||
|
||||
for(Map.Entry<Long,Channel> entry: mAllocatedChannelFrequencyMap.entrySet())
|
||||
{
|
||||
frequencyToUpdate = entry.getKey();
|
||||
updateCallEventDetails(entry.getKey(), CHANNEL_START_REJECTED);
|
||||
break;
|
||||
if(channel == entry.getValue() && entry.getKey() != null)
|
||||
{
|
||||
frequencyToUpdate = entry.getKey();
|
||||
setChannelStartRejected(entry.getKey());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(frequencyToUpdate > 0)
|
||||
{
|
||||
mAllocatedChannelFrequencyMap.remove(frequencyToUpdate);
|
||||
|
||||
//Unlock the frequency in the channel rotation monitor
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToUpdate));
|
||||
}
|
||||
|
||||
//Add the traffic channel back to the queue to be reused
|
||||
if(!mAvailableTrafficChannels.contains(channel))
|
||||
{
|
||||
mAvailableTrafficChannels.add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
if(frequencyToUpdate > 0)
|
||||
finally
|
||||
{
|
||||
mAllocatedTrafficChannelFrequencyMap.remove(frequencyToUpdate);
|
||||
|
||||
//Unlock the frequency in the channel rotation monitor
|
||||
getInterModuleEventBus().post(FrequencyLockChangeRequest.unlock(frequencyToUpdate));
|
||||
}
|
||||
|
||||
//Add the traffic channel back to the queue to be reused
|
||||
if(!mAvailableTrafficChannelQueue.contains(channel))
|
||||
{
|
||||
mAvailableTrafficChannelQueue.add(channel);
|
||||
mLock.unlock();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -41,6 +42,9 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
||||
private boolean mUseCompressedTalkgroups = false;
|
||||
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
|
||||
|
||||
@JsonIgnore
|
||||
private DecodeEvent mChannelGrantEvent;
|
||||
|
||||
public DecodeConfigDMR()
|
||||
{
|
||||
}
|
||||
@@ -191,4 +195,32 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
||||
{
|
||||
mTimeslotMap.add(timeslotFrequency);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
/**
|
||||
* Original channel grant event. This is used for trunking systems to pass the original channel grant event to
|
||||
* the decoder state on channel startup.
|
||||
*/
|
||||
public DecodeEvent getChannelGrantEvent()
|
||||
{
|
||||
return mChannelGrantEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the original channel grant event.
|
||||
* @param channelGrantEvent to set on the decoder state.
|
||||
*/
|
||||
public void setChannelGrantEvent(DecodeEvent channelGrantEvent)
|
||||
{
|
||||
mChannelGrantEvent = channelGrantEvent;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
/**
|
||||
* Indicates if this decode config has an original channel grant event to preload.
|
||||
*/
|
||||
public boolean hasChannelGrantEvent()
|
||||
{
|
||||
return mChannelGrantEvent != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,20 +24,38 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manages alternate CRC masks that may be employed in a system.
|
||||
* Manages alternate CRC masks that may be employed in a system. Some vendors employ non-standard CRC checksum masks
|
||||
* or initial fill values. This utility tracks these non-standard values by observation count and time and accepts
|
||||
* these alternate mask values to automatically correct or validate messages when those messages employ these
|
||||
* alternate mask values. Alternate mask values must be observed at least 3x times in a 2-minute window against the
|
||||
* same message content, in order for the alternate mask value to be used. Tracked values that do not get another
|
||||
* observation count within a 2-minute window will be ejected from the cache and excluded as a valid CRC mask candidate.
|
||||
*
|
||||
* Notes: different alternate CRC mask values can be used independently in each timeslot. Further, I've seen multiple
|
||||
* CRC mask values being employed within the same timeslot. This indicates that systems can use multiple CRC mask
|
||||
* values against CSBK messages and some other function is involved in determining which CRC mask value to use when
|
||||
* checking the CRC of the transmitted CSBK ... fun and games!
|
||||
*/
|
||||
public class DmrCrcMaskManager
|
||||
{
|
||||
private Logger LOGGER = LoggerFactory.getLogger(DmrCrcMaskManager.class);
|
||||
private Map<Integer, ResidualCrcMaskTracker> mCsbkResidualTrackerMap = new TreeMap<>();
|
||||
private int mDominantMask = 0;
|
||||
private Map<Integer, MaskTracker> mCsbkTrackerMapTS1 = new TreeMap<>();
|
||||
private Map<Integer, MaskTracker> mCsbkTrackerMapTS2 = new TreeMap<>();
|
||||
private Set<MaskTracker> mSortedTrackersTS1 = new TreeSet<>();
|
||||
private Set<MaskTracker> mSortedTrackersTS2 = new TreeSet<>();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -46,35 +64,27 @@ public class DmrCrcMaskManager
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deep copy (ie clone) of all tracked values
|
||||
* @return list of tracker clones.
|
||||
*/
|
||||
public List<ResidualCrcMaskTracker> cloneTrackers()
|
||||
{
|
||||
List<ResidualCrcMaskTracker>trackers = new ArrayList<>();
|
||||
for(ResidualCrcMaskTracker tracker: mCsbkResidualTrackerMap.values())
|
||||
{
|
||||
trackers.add(tracker.clone());
|
||||
}
|
||||
|
||||
return trackers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the current tracked CRC values and counts.
|
||||
*/
|
||||
public void log()
|
||||
{
|
||||
List<ResidualCrcMaskTracker> trackers = new ArrayList<>(mCsbkResidualTrackerMap.values());
|
||||
Collections.sort(trackers);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("DMR CRC Mask Manager - Current Tracked Values\n");
|
||||
for(ResidualCrcMaskTracker tracker: trackers)
|
||||
|
||||
sb.append("DMR CRC Mask Manager - TS1 Current Tracked Values\n");
|
||||
for(MaskTracker tracker: mSortedTrackersTS1)
|
||||
{
|
||||
sb.append("\tTracked Value: ").append(String.format("0x%04X", tracker.getTrackedValue()));
|
||||
sb.append(" Count:").append(tracker.getCount());
|
||||
sb.append(" Count:").append(tracker.getObservationCount());
|
||||
sb.append(" Last Observed: " + new Date(tracker.getLastUpdated())).append("\n");
|
||||
}
|
||||
|
||||
sb.append("DMR CRC Mask Manager - TS2 Current Tracked Values\n");
|
||||
for(MaskTracker tracker: mSortedTrackersTS2)
|
||||
{
|
||||
sb.append("\tTracked Value: ").append(String.format("0x%04X", tracker.getTrackedValue()));
|
||||
sb.append(" Count:").append(tracker.getObservationCount());
|
||||
sb.append(" Last Observed: " + new Date(tracker.getLastUpdated())).append("\n");
|
||||
}
|
||||
|
||||
@@ -83,104 +93,139 @@ public class DmrCrcMaskManager
|
||||
|
||||
/**
|
||||
* Checks the CSBK message that has a failed CRC checksum to detect if an alternate CRC mask value is employed and
|
||||
* if so, attempts to correct the message by using the alternate mask value once the alternate mask value is
|
||||
* detected to be used consistently enough.
|
||||
* @param csbk to re-check.
|
||||
* if so, attempts to correct the message.
|
||||
* @param csbk to re-check using the currently tracked mask value.
|
||||
*/
|
||||
public void check(CSBKMessage csbk)
|
||||
{
|
||||
if(!csbk.isValid())
|
||||
{
|
||||
int residual = CRCDMR.calculateResidual(csbk.getMessage(), 0, 80);
|
||||
int alternateMask = CRCDMR.calculateResidual(csbk.getMessage(), 0, 80);
|
||||
|
||||
if(mCsbkResidualTrackerMap.containsKey(residual))
|
||||
Map<Integer,MaskTracker> map;
|
||||
Set<MaskTracker> sortedSet;
|
||||
|
||||
if(csbk.getTimeslot() == 1)
|
||||
{
|
||||
mCsbkResidualTrackerMap.get(residual).increment();
|
||||
map = mCsbkTrackerMapTS1;
|
||||
sortedSet = mSortedTrackersTS1;
|
||||
}
|
||||
else
|
||||
{
|
||||
mCsbkResidualTrackerMap.put(residual, new ResidualCrcMaskTracker(residual));
|
||||
map = mCsbkTrackerMapTS2;
|
||||
sortedSet = mSortedTrackersTS2;
|
||||
}
|
||||
|
||||
List<ResidualCrcMaskTracker> trackers = new ArrayList<>(mCsbkResidualTrackerMap.values());
|
||||
Collections.sort(trackers);
|
||||
|
||||
if(trackers.size() > 5)
|
||||
if(map.containsKey(alternateMask))
|
||||
{
|
||||
mCsbkResidualTrackerMap.remove(trackers.get(0).getTrackedValue());
|
||||
}
|
||||
MaskTracker matchingTracker = map.get(alternateMask);
|
||||
matchingTracker.increment(csbk.getTimestamp());
|
||||
|
||||
if(trackers.size() > 0)
|
||||
{
|
||||
ResidualCrcMaskTracker dominant = trackers.get(trackers.size() - 1);
|
||||
|
||||
if(dominant.isValid())
|
||||
if(matchingTracker.isValid())
|
||||
{
|
||||
mDominantMask = dominant.getTrackedValue();
|
||||
csbk.checkCRC(matchingTracker.getTrackedValue());
|
||||
}
|
||||
}
|
||||
|
||||
if(mDominantMask != 0)
|
||||
else
|
||||
{
|
||||
csbk.checkCRC(mDominantMask);
|
||||
//
|
||||
// if(csbk.isValid())
|
||||
// {
|
||||
// LOGGER.info("CSBK fixed using alternate mask: " + Integer.toHexString(mDominantMask).toUpperCase());
|
||||
// }
|
||||
//Attempt to use other tracked mask values in order of observed count and also remove stale trackers
|
||||
boolean found = false;
|
||||
MaskTracker next;
|
||||
Iterator<MaskTracker> it = sortedSet.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
next = it.next();
|
||||
|
||||
//If the tracker is stale, remove it
|
||||
if(next.isStale(csbk.getTimestamp()))
|
||||
{
|
||||
it.remove();
|
||||
map.remove(next.getTrackedValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we haven't found a match yet, attempt to correct the message using this tracked mask value
|
||||
if(!found)
|
||||
{
|
||||
csbk.checkCRC(next.getTrackedValue());
|
||||
|
||||
if(csbk.isValid())
|
||||
{
|
||||
found = true;
|
||||
next.increment(csbk.getTimestamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If we didn't find a perfect match or even a close match (with 1 bit error), create a new tracked value.
|
||||
if(!found)
|
||||
{
|
||||
MaskTracker tracker = new MaskTracker(alternateMask, csbk.getTimestamp());
|
||||
map.put(alternateMask, tracker);
|
||||
sortedSet.add(tracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Residual CRC mask value tracker. Tracks the number of observances of a residual CRC calculated value to detect
|
||||
* when an alternate CRC mask pattern is employed and automatically correct CRC values for messages.
|
||||
* Alternate CRC mask value tracker. Tracks the number of observances of the mask value to detect when an alternate
|
||||
* CRC mask pattern is employed and can then be used to automatically correct CRC values for messages.
|
||||
*/
|
||||
public class ResidualCrcMaskTracker implements Comparable<ResidualCrcMaskTracker>
|
||||
public class MaskTracker implements Comparable<MaskTracker>
|
||||
{
|
||||
private static final long STALENESS_TIME_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
|
||||
private static final int STALENESS_COUNT_THRESHOLD = 100;
|
||||
private int mTrackedValue;
|
||||
private int mCount;
|
||||
private int mObservationCount;
|
||||
private int mStalenessCount;
|
||||
private long mLastUpdated;
|
||||
|
||||
/**
|
||||
* Constructs an instance for the specified residual, sets the count to one and sets the last update to now.
|
||||
* Constructs an instance for the specified mask, sets the observation count to one and sets the last update to
|
||||
* the supplied timestamp.
|
||||
* @param residual value to track.
|
||||
* @param timestamp for the observation
|
||||
*/
|
||||
public ResidualCrcMaskTracker(int residual)
|
||||
public MaskTracker(int residual, long timestamp)
|
||||
{
|
||||
mTrackedValue = residual;
|
||||
mCount = 1;
|
||||
mLastUpdated = System.currentTimeMillis();
|
||||
mObservationCount = 1;
|
||||
mLastUpdated = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of this instance.
|
||||
* @return cloned instance.
|
||||
*/
|
||||
public ResidualCrcMaskTracker clone()
|
||||
{
|
||||
ResidualCrcMaskTracker clone = new ResidualCrcMaskTracker(getTrackedValue());
|
||||
clone.mCount = mCount;
|
||||
clone.mLastUpdated = mLastUpdated;
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of number of times this residual has been observed.
|
||||
* Count of observations for this mask value.
|
||||
* @return count
|
||||
*/
|
||||
public int getCount()
|
||||
public int getObservationCount()
|
||||
{
|
||||
return mCount;
|
||||
return mObservationCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the residual value has been observed at least 3x times indicating that it is possibly valid.
|
||||
* @return
|
||||
* Indicates that the residual value has been observed at least 3x times within the preceding 2 minute window,
|
||||
* indicating that it is (likely) valid.
|
||||
* @return true if the tracked mask value is valid.
|
||||
*/
|
||||
public boolean isValid()
|
||||
{
|
||||
return mCount >= 3;
|
||||
return mObservationCount >= 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this tracker is stale, relative to the supplied timestamp. Stale indicates that this tracked
|
||||
* value hasn't been observed in the preceding 2 minutes, or has been checked for staleness more than 100 times
|
||||
* without an updated observation.
|
||||
* @param timestamp to compare for staleness check.
|
||||
* @return true if this tracker is stale.
|
||||
*/
|
||||
public boolean isStale(long timestamp)
|
||||
{
|
||||
mStalenessCount++;
|
||||
return mStalenessCount > STALENESS_COUNT_THRESHOLD ||
|
||||
Math.abs(timestamp - mLastUpdated) > STALENESS_TIME_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,38 +237,44 @@ public class DmrCrcMaskManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Residual tracked value.
|
||||
* @return residual
|
||||
* Tracked alternate mask value.
|
||||
* @return tracked mask value
|
||||
*/
|
||||
public int getTrackedValue()
|
||||
{
|
||||
return mTrackedValue;
|
||||
}
|
||||
|
||||
public void increment()
|
||||
/**
|
||||
* Increment the observation count and use the supplied timestamp as the last updated timestamp.
|
||||
* @param timestamp for the observed message.
|
||||
*/
|
||||
public void increment(long timestamp)
|
||||
{
|
||||
mCount++;
|
||||
//Prevent rollover by keeping value at max integer value.
|
||||
if(mCount < 0)
|
||||
mStalenessCount = 0;
|
||||
mObservationCount++;
|
||||
//Detect integer rollover and apply max integer value.
|
||||
if(mObservationCount < 0)
|
||||
{
|
||||
mCount = Integer.MAX_VALUE;
|
||||
mObservationCount = Integer.MAX_VALUE;
|
||||
}
|
||||
mLastUpdated = System.currentTimeMillis();
|
||||
mLastUpdated = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom sort order by count and then by last updated timestamp.
|
||||
* Custom (reversed) sort order by largest count and then by latest updated timestamp.
|
||||
* @param o the object to be compared.
|
||||
* @return
|
||||
* @return comparison value.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(ResidualCrcMaskTracker o)
|
||||
public int compareTo(MaskTracker o)
|
||||
{
|
||||
int comparison = Integer.compare(getCount(), o.getCount());
|
||||
//Multiply by -1 to apply a reverse ordering
|
||||
int comparison = Integer.compare(getObservationCount(), o.getObservationCount()) * -1;
|
||||
|
||||
if(comparison == 0)
|
||||
{
|
||||
comparison = Long.compare(getLastUpdated(), o.getLastUpdated());
|
||||
comparison = Long.compare(getLastUpdated(), o.getLastUpdated()) * -1;
|
||||
}
|
||||
|
||||
return comparison;
|
||||
|
||||
@@ -74,4 +74,12 @@ public class DMRAbsoluteChannel extends DMRChannel
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DMRChannel getSisterTimeslot()
|
||||
{
|
||||
DMRAbsoluteChannel other = new DMRAbsoluteChannel(getChannelNumber(), getTimeslot() == 1 ? 2 : 1,
|
||||
getDownlinkFrequency(), getUplinkFrequency());
|
||||
return other;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
mTimeslot = timeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a channel of the same type, but for the alternate timeslot.
|
||||
* @return sister timeslot channel
|
||||
*/
|
||||
public abstract DMRChannel getSisterTimeslot();
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
@@ -160,6 +166,15 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
|
||||
mTimeslotFrequency = timeslotFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* TimeslotFrequency for this channel.
|
||||
* @return timeslot frequency instance, or null.
|
||||
*/
|
||||
public TimeslotFrequency getTimeslotFrequency()
|
||||
{
|
||||
return mTimeslotFrequency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
|
||||
@@ -69,4 +69,25 @@ public class DMRLsn extends DMRChannel
|
||||
return ((getChannelNumber() - 1) * 2) + getTimeslot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a channel of the same type, but for the alternate timeslot.
|
||||
* @return sister timeslot channel
|
||||
*/
|
||||
public DMRChannel getSisterTimeslot()
|
||||
{
|
||||
int lsn;
|
||||
|
||||
if(getTimeslot() == 1)
|
||||
{
|
||||
lsn = getLsn() + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
lsn = getLsn() - 1;
|
||||
}
|
||||
|
||||
DMRLsn other = new DMRLsn(lsn);
|
||||
other.setTimeslotFrequency(getTimeslotFrequency());
|
||||
return other;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,4 +55,12 @@ public class DMRTier3Channel extends DMRChannel
|
||||
sb.append(" CHANID:").append(getChannelId());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DMRChannel getSisterTimeslot()
|
||||
{
|
||||
DMRTier3Channel other = new DMRTier3Channel(getChannelNumber(), getTimeslot() == 1 ? 2 : 1);
|
||||
other.setTimeslotFrequency(getTimeslotFrequency());
|
||||
return other;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.dmr.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.
|
||||
*
|
||||
* This event class is send traffic channel grant details to the traffic channel manager to allocate a traffic channel.
|
||||
*/
|
||||
public class DMRChannelGrantEvent extends DMRDecodeEvent
|
||||
{
|
||||
private boolean mEncrypted;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param decodeEventType for the event
|
||||
* @param timestamp for the event
|
||||
*/
|
||||
public DMRChannelGrantEvent(DecodeEventType decodeEventType, long timestamp)
|
||||
{
|
||||
super(decodeEventType, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the channel grant was for an encrypted call
|
||||
*/
|
||||
public boolean isEncrypted()
|
||||
{
|
||||
return mEncrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encrypte state for the call
|
||||
* @param encrypted
|
||||
*/
|
||||
public void setEncrypted(boolean encrypted)
|
||||
{
|
||||
mEncrypted = encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decode event builder with the specified start timestamp.
|
||||
* @param timeStart for the event
|
||||
* @return builder
|
||||
*/
|
||||
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
return new DMRChannelGrantDecodeEventBuilder(decodeEventType, timeStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder pattern for constructing decode events.
|
||||
*/
|
||||
public static class DMRChannelGrantDecodeEventBuilder
|
||||
{
|
||||
protected long mTimeStart;
|
||||
protected long mDuration;
|
||||
protected DecodeEventType mDecodeEventType;
|
||||
protected IdentifierCollection mIdentifierCollection;
|
||||
protected DMRChannel mChannel;
|
||||
protected String mDetails;
|
||||
protected boolean mEncrypted;
|
||||
|
||||
/**
|
||||
* Constructs a builder instance with the specified start time in milliseconds
|
||||
*
|
||||
* @param timeStart
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
|
||||
{
|
||||
mDecodeEventType = decodeEventType;
|
||||
mTimeStart = timeStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encrypted state for the channel/timeslot
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder encrypted(boolean encrypted)
|
||||
{
|
||||
mEncrypted = encrypted;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration value
|
||||
* @param duration in milliseconds
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder duration(long duration)
|
||||
{
|
||||
mDuration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration value using the end - start timestamps
|
||||
* @param timestamp for end of event
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder end(long timestamp)
|
||||
{
|
||||
mDuration = timestamp - mTimeStart;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel descriptor for this event
|
||||
* @param channel
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel)
|
||||
{
|
||||
mChannel = channel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the identifier collection.
|
||||
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
|
||||
* alias list configuration name.
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder identifiers(IdentifierCollection identifierCollection)
|
||||
{
|
||||
mIdentifierCollection = identifierCollection;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the details for the event
|
||||
* @param details
|
||||
*/
|
||||
public DMRChannelGrantDecodeEventBuilder details(String details)
|
||||
{
|
||||
mDetails = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the decode event
|
||||
*/
|
||||
public DMRChannelGrantEvent build()
|
||||
{
|
||||
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mDecodeEventType, mTimeStart);
|
||||
decodeEvent.setChannelDescriptor(mChannel);
|
||||
decodeEvent.setTimeslot(mChannel.getTimeslot());
|
||||
decodeEvent.setDetails(mDetails);
|
||||
decodeEvent.setDuration(mDuration);
|
||||
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
||||
return decodeEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraTraff
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxOpenModeVoiceChannelUpdate;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAdvantageModeVoiceChannelUpdate;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusCSBKO_60;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowAnnouncement;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityPlusDataRevertWindowGrant;
|
||||
@@ -277,7 +279,12 @@ public class CSBKMessageFactory
|
||||
case MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT:
|
||||
csbk = new CapacityPlusDataRevertWindowGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE:
|
||||
csbk = new CapacityMaxOpenModeVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE:
|
||||
csbk = new CapacityMaxAdvantageModeVoiceChannelUpdate(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
case MOTOROLA_CONPLUS_CSBKO_10:
|
||||
csbk = new ConnectPlusOTAAnnouncement(pattern, message, cach, slotType, timestamp, timeslot);
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -77,7 +77,9 @@ public enum Opcode
|
||||
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT(Vendor.MOTOROLA_CONNECT_PLUS, 28, "ENHANCED DATA REVERT WINDOW ANNOUNCEMENT"),
|
||||
MOTOROLA_CONPLUS_DATA_WINDOW_GRANT(Vendor.MOTOROLA_CONNECT_PLUS, 29, "ENHANCED DATA REVERT WINDOW GRANT"),
|
||||
|
||||
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "ALOHA"),
|
||||
MOTOROLA_CAPMAX_ALOHA(Vendor.MOTOROLA_CAPACITY_PLUS, 25, "CAP MAX ALOHA"),
|
||||
MOTOROLA_CAPMAX_CHANNEL_UPDATE_OPEN_MODE(Vendor.MOTOROLA_CAPACITY_PLUS, 33, "CAP MAX CHAN UPD OPEN MODE"),
|
||||
MOTOROLA_CAPMAX_CHANNEL_UPDATE_ADVANTAGE_MODE(Vendor.MOTOROLA_CAPACITY_PLUS, 34, "CAP MAX CHAN UPD ADV MODE"),
|
||||
|
||||
MOTOROLA_CAPPLUS_CALL_ALERT(Vendor.MOTOROLA_CAPACITY_PLUS, 31, "CALL ALERT"),
|
||||
MOTOROLA_CAPPLUS_CALL_ALERT_ACK(Vendor.MOTOROLA_CAPACITY_PLUS, 32, "CALL ALERT ACK"),
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Motorola Capacity Max Advantage Mode - Voice Channel Update (Opcode 34 / 0x22)
|
||||
* Note: it appears that Capacity Max Talkgroups in advantage mode are only 10-bits long in range of 0 - 1023.
|
||||
*/
|
||||
public class CapacityMaxAdvantageModeVoiceChannelUpdate extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
{
|
||||
private static final int[] CHANNEL_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
|
||||
private static final int[] TALKGROUP_CH1_TS1 = new int[]{28, 29, 30, 31, 32, 33, 34, 35, 36, 37};
|
||||
private static final int[] TALKGROUP_CH1_TS2 = new int[]{38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
private static final int[] CHANNEL_2 = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59};
|
||||
private static final int[] TALKGROUP_CH2_TS1 = new int[]{60, 61, 62, 63, 64, 65, 66, 67, 68, 69};
|
||||
private static final int[] TALKGROUP_CH2_TS2 = new int[]{70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||
private TalkgroupIdentifier mTalkgroupCH1TS1;
|
||||
private TalkgroupIdentifier mTalkgroupCH1TS2;
|
||||
private TalkgroupIdentifier mTalkgroupCH2TS1;
|
||||
private TalkgroupIdentifier mTalkgroupCH2TS2;
|
||||
private DMRTier3Channel mChannel1TS1;
|
||||
private DMRTier3Channel mChannel1TS2;
|
||||
private DMRTier3Channel mChannel2TS1;
|
||||
private DMRTier3Channel mChannel2TS2;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param syncPattern for the CSBK
|
||||
* @param message bits
|
||||
* @param cach for the DMR burst
|
||||
* @param slotType for this message
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxAdvantageModeVoiceChannelUpdate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||
}
|
||||
|
||||
sb.append(" CSBK CAP-MAX MODE:ADVANTAGE CHANNEL UPDATE");
|
||||
|
||||
if(hasChannel1Timeslot1())
|
||||
{
|
||||
sb.append(" TG1:").append(getTalkgroupCH1TS1());
|
||||
sb.append(" ON").append(getChannel1TS1());
|
||||
}
|
||||
if(hasChannel1Timeslot2())
|
||||
{
|
||||
sb.append(" TG2:").append(getTalkgroupCH1TS2());
|
||||
sb.append(" ON").append(getChannel1TS2());
|
||||
}
|
||||
|
||||
if(hasChannel2Timeslot1())
|
||||
{
|
||||
sb.append(" TG3:").append(getTalkgroupCH1TS1());
|
||||
sb.append(" ON").append(getChannel2TS1());
|
||||
}
|
||||
if(hasChannel2Timeslot2())
|
||||
{
|
||||
sb.append(" TG4:").append(getTalkgroupCH1TS2());
|
||||
sb.append(" ON").append(getChannel2TS2());
|
||||
}
|
||||
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message contains activity on channel 1 timeslot 1.
|
||||
*/
|
||||
public boolean hasChannel1Timeslot1()
|
||||
{
|
||||
return getMessage().getInt(TALKGROUP_CH1_TS1) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message contains activity on channel 1 timeslot 2.
|
||||
*/
|
||||
public boolean hasChannel1Timeslot2()
|
||||
{
|
||||
return getMessage().getInt(TALKGROUP_CH1_TS2) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message contains activity on channel 2 timeslot 1.
|
||||
*/
|
||||
public boolean hasChannel2Timeslot1()
|
||||
{
|
||||
return getMessage().getInt(TALKGROUP_CH2_TS1) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message contains activity on channel 2 timeslot 2.
|
||||
*/
|
||||
public boolean hasChannel2Timeslot2()
|
||||
{
|
||||
return getMessage().getInt(TALKGROUP_CH2_TS2) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup that is active on channel 1 timeslot 1.
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroupCH1TS1()
|
||||
{
|
||||
if(mTalkgroupCH1TS1 == null)
|
||||
{
|
||||
mTalkgroupCH1TS1 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_CH1_TS1));
|
||||
}
|
||||
|
||||
return mTalkgroupCH1TS1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup that is active on channel 1 timeslot 2.
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroupCH1TS2()
|
||||
{
|
||||
if(mTalkgroupCH1TS2 == null)
|
||||
{
|
||||
mTalkgroupCH1TS2 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_CH1_TS2));
|
||||
}
|
||||
|
||||
return mTalkgroupCH1TS2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup that is active on channel 2 timeslot 1.
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroupCH2TS1()
|
||||
{
|
||||
if(mTalkgroupCH2TS1 == null)
|
||||
{
|
||||
mTalkgroupCH2TS1 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_CH2_TS1));
|
||||
}
|
||||
|
||||
return mTalkgroupCH2TS1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup that is active on channel 2 timeslot 2.
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroupCH2TS2()
|
||||
{
|
||||
if(mTalkgroupCH2TS2 == null)
|
||||
{
|
||||
mTalkgroupCH2TS2 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_CH2_TS2));
|
||||
}
|
||||
|
||||
return mTalkgroupCH2TS2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 1 Timeslot 1.
|
||||
*/
|
||||
public DMRTier3Channel getChannel1TS1()
|
||||
{
|
||||
if(mChannel1TS1 == null)
|
||||
{
|
||||
mChannel1TS1 = new DMRTier3Channel(getMessage().getInt(CHANNEL_1), 1);
|
||||
}
|
||||
|
||||
return mChannel1TS1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 1 Timeslot 2.
|
||||
*/
|
||||
public DMRTier3Channel getChannel1TS2()
|
||||
{
|
||||
if(mChannel1TS2 == null)
|
||||
{
|
||||
mChannel1TS2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_1), 2);
|
||||
}
|
||||
|
||||
return mChannel1TS2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 2 Timeslot 1.
|
||||
*/
|
||||
public DMRTier3Channel getChannel2TS1()
|
||||
{
|
||||
if(mChannel2TS1 == null)
|
||||
{
|
||||
mChannel2TS1 = new DMRTier3Channel(getMessage().getInt(CHANNEL_2), 1);
|
||||
}
|
||||
|
||||
return mChannel2TS1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel 2 Timeslot 2.
|
||||
*/
|
||||
public DMRTier3Channel getChannel2TS2()
|
||||
{
|
||||
if(mChannel2TS2 == null)
|
||||
{
|
||||
mChannel2TS2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_2), 2);
|
||||
}
|
||||
|
||||
return mChannel2TS2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a timeslot frequency map for the DMR channel
|
||||
*
|
||||
* @param timeslotFrequencies that match the logical timeslots
|
||||
*/
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(hasChannel1Timeslot1())
|
||||
{
|
||||
getChannel1TS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel1Timeslot2())
|
||||
{
|
||||
getChannel1TS2().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel2Timeslot1())
|
||||
{
|
||||
getChannel2TS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasChannel2Timeslot2())
|
||||
{
|
||||
getChannel2TS2().apply(timeslotFrequencies);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
//Since both timeslots share the same LCN, we use just TS1's channel for both channel 1 and channel 2
|
||||
if((hasChannel1Timeslot1() || hasChannel1Timeslot2()) && (hasChannel2Timeslot1() || hasChannel2Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel1TS1().getChannelId(), getChannel2TS1().getChannelId()};
|
||||
}
|
||||
else if((hasChannel1Timeslot1() || hasChannel1Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel1TS1().getChannelId()};
|
||||
}
|
||||
else if((hasChannel2Timeslot1() || hasChannel2Timeslot2()))
|
||||
{
|
||||
return new int[]{getChannel2TS1().getChannelId()};
|
||||
}
|
||||
|
||||
return getChannel1TS1().getLogicalChannelNumbers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
|
||||
if(hasChannel1Timeslot1())
|
||||
{
|
||||
mIdentifiers.add(getTalkgroupCH1TS1());
|
||||
mIdentifiers.add(getChannel1TS1());
|
||||
}
|
||||
|
||||
if(hasChannel1Timeslot2())
|
||||
{
|
||||
mIdentifiers.add(getTalkgroupCH1TS2());
|
||||
mIdentifiers.add(getChannel1TS2());
|
||||
}
|
||||
|
||||
if(hasChannel2Timeslot1())
|
||||
{
|
||||
mIdentifiers.add(getTalkgroupCH2TS1());
|
||||
mIdentifiers.add(getChannel2TS1());
|
||||
}
|
||||
|
||||
if(hasChannel2Timeslot2())
|
||||
{
|
||||
mIdentifiers.add(getTalkgroupCH2TS2());
|
||||
mIdentifiers.add(getChannel2TS2());
|
||||
}
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRTier3Channel;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Motorola Capacity Max Open System Mode - Voice Channel Update (Opcode 33 / 0x21)
|
||||
*
|
||||
* Opcode 33 and 34 are very similar. However, Opcode 33 appears to support full 24-bit group identifiers whereas
|
||||
* Opcode 34 only supports 10-bit group identifiers.
|
||||
* <p>
|
||||
* Likely incomplete implementation.
|
||||
*/
|
||||
public class CapacityMaxOpenModeVoiceChannelUpdate extends CSBKMessage implements ITimeslotFrequencyReceiver
|
||||
{
|
||||
private static final int[] CHANNEL_NUMBER = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
|
||||
private static final int[] TALKGROUP_TS1 = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||
private static final int[] TALKGROUP_TS2 = new int[]{56, 57, 58, 59, 60, 61, 63, 64, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||
private TalkgroupIdentifier mTalkgroupTS1;
|
||||
private TalkgroupIdentifier mTalkgroupTS2;
|
||||
private DMRTier3Channel mChannelTS1;
|
||||
private DMRTier3Channel mChannelTS2;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param syncPattern for the CSBK
|
||||
* @param message bits
|
||||
* @param cach for the DMR burst
|
||||
* @param slotType for this message
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxOpenModeVoiceChannelUpdate(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||
{
|
||||
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||
}
|
||||
|
||||
sb.append(" CSBK CAP-MAX MODE:OPEN-SYSTEM CHANNEL UPDATE");
|
||||
|
||||
if(hasTimeslot1())
|
||||
{
|
||||
sb.append(" TG1:").append(getTalkgroupTS1());
|
||||
sb.append(" ON").append(getChannelTS1());
|
||||
}
|
||||
if(hasTimeslot2())
|
||||
{
|
||||
sb.append(" TG2:").append(getTalkgroupTS2());
|
||||
sb.append(" ON").append(getChannelTS2());
|
||||
}
|
||||
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message contains channel update for timeslot 1.
|
||||
*/
|
||||
public boolean hasTimeslot1()
|
||||
{
|
||||
return getMessage().getInt(TALKGROUP_TS1) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message contains channel update for timeslot 2.
|
||||
*/
|
||||
public boolean hasTimeslot2()
|
||||
{
|
||||
return getMessage().getInt(TALKGROUP_TS2) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup that is active on the channel for timeslot 1.
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroupTS1()
|
||||
{
|
||||
if(mTalkgroupTS1 == null)
|
||||
{
|
||||
mTalkgroupTS1 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_TS1));
|
||||
}
|
||||
|
||||
return mTalkgroupTS1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup that is active on the channel for timeslot 2.
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroupTS2()
|
||||
{
|
||||
if(mTalkgroupTS2 == null)
|
||||
{
|
||||
mTalkgroupTS2 = DMRTalkgroup.create(getMessage().getInt(TALKGROUP_TS2));
|
||||
}
|
||||
|
||||
return mTalkgroupTS2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel for timeslot 1
|
||||
*/
|
||||
public DMRTier3Channel getChannelTS1()
|
||||
{
|
||||
if(mChannelTS1 == null)
|
||||
{
|
||||
mChannelTS1 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), 1);
|
||||
}
|
||||
|
||||
return mChannelTS1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel for timeslot 2
|
||||
*/
|
||||
public DMRTier3Channel getChannelTS2()
|
||||
{
|
||||
if(mChannelTS2 == null)
|
||||
{
|
||||
mChannelTS2 = new DMRTier3Channel(getMessage().getInt(CHANNEL_NUMBER), 2);
|
||||
}
|
||||
|
||||
return mChannelTS2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a timeslot frequency map for the DMR channel
|
||||
*
|
||||
* @param timeslotFrequencies that match the logical timeslots
|
||||
*/
|
||||
@Override
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
if(hasTimeslot1())
|
||||
{
|
||||
getChannelTS1().apply(timeslotFrequencies);
|
||||
}
|
||||
if(hasTimeslot2())
|
||||
{
|
||||
getChannelTS2().apply(timeslotFrequencies);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
boolean ts1 = hasTimeslot1();
|
||||
boolean ts2 = hasTimeslot2();
|
||||
|
||||
if(ts1 && ts2)
|
||||
{
|
||||
return new int[]{getChannelTS1().getChannelNumber(), getChannelTS2().getChannelNumber()};
|
||||
}
|
||||
else if(ts1)
|
||||
{
|
||||
return new int[]{getChannelTS1().getChannelNumber()};
|
||||
}
|
||||
else if(ts2)
|
||||
{
|
||||
return new int[]{getChannelTS2().getChannelNumber()};
|
||||
}
|
||||
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
|
||||
if(hasTimeslot1())
|
||||
{
|
||||
mIdentifiers.add(getChannelTS1());
|
||||
mIdentifiers.add(getTalkgroupTS1());
|
||||
}
|
||||
if(hasTimeslot2())
|
||||
{
|
||||
mIdentifiers.add(getChannelTS2());
|
||||
mIdentifiers.add(getTalkgroupTS2());
|
||||
}
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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,15 +21,25 @@ package io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.DmrRestLsn;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Capacity+ Site Status CSBKO=62 Message
|
||||
@@ -43,6 +53,8 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
private static final int[] LSN_VOICE_BITMAP = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||
private static final int LSN_1_8_BITMAP_START = 24;
|
||||
|
||||
private Map<Integer,IntegerIdentifier> mActiveIdentifierMap;
|
||||
private Map<Integer,DMRLsn> mActiveLsnMap;
|
||||
private DmrRestLsn mRestChannel;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
@@ -96,172 +108,202 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message has voice talkgroup activity.
|
||||
*
|
||||
* @return true if voice activity.
|
||||
*/
|
||||
private boolean hasVoiceTalkgroups()
|
||||
{
|
||||
return getSegmentIndicator().isFirst() && getMessage().getInt(LSN_VOICE_BITMAP) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the call activity from the first or first/last fragment.
|
||||
* @return description of call activity.
|
||||
*/
|
||||
private String getActivityFragments()
|
||||
{
|
||||
if(getMessage().get(48) && getMessage().get(56))
|
||||
{
|
||||
int a = 0; //debug
|
||||
}
|
||||
int[] talkgroups = new int[17];
|
||||
int[] radios = new int[17];
|
||||
boolean hasIdentifier = false;
|
||||
|
||||
int pointer = LSN_1_8_BITMAP_START;
|
||||
|
||||
//Process voice LSNs 1-8
|
||||
if(getMessage().getInt(BYTE, pointer) > 0)
|
||||
{
|
||||
int lowLsnBitmap = pointer;
|
||||
pointer += 8;
|
||||
|
||||
for(int x = lowLsnBitmap; x < (lowLsnBitmap + 8); x++)
|
||||
{
|
||||
int lsn = x - lowLsnBitmap + 1;
|
||||
|
||||
if(getMessage().get(x))
|
||||
{
|
||||
hasIdentifier = true;
|
||||
|
||||
if(pointer <= 72)
|
||||
{
|
||||
talkgroups[lsn] = getMessage().getInt(BYTE, pointer);
|
||||
pointer += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
talkgroups[lsn] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pointer += 8;
|
||||
}
|
||||
|
||||
//Process voice LSNs 9 - 16
|
||||
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
|
||||
{
|
||||
hasIdentifier = true;
|
||||
int highLsnBitmap = pointer;
|
||||
|
||||
pointer += 8;
|
||||
|
||||
for(int x = highLsnBitmap; x < (highLsnBitmap + 8); x++)
|
||||
{
|
||||
int lsn = x - highLsnBitmap + 1 + 8;
|
||||
|
||||
if(getMessage().get(x))
|
||||
{
|
||||
if(pointer <= 72)
|
||||
{
|
||||
talkgroups[lsn] = getMessage().getInt(BYTE, pointer);
|
||||
pointer += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
talkgroups[lsn] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pointer += 8;
|
||||
}
|
||||
|
||||
//Process Radio IDs - first bit in radio options byte is set to indicate more activity
|
||||
if(pointer <= 72 && getMessage().get(pointer))
|
||||
{
|
||||
hasIdentifier = true;
|
||||
pointer += 8;
|
||||
|
||||
//If we have the data revert channel bitmap ...
|
||||
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
|
||||
{
|
||||
int lowDataLsnBitmap = pointer;
|
||||
|
||||
pointer += 8;
|
||||
|
||||
for(int x = lowDataLsnBitmap; x < (lowDataLsnBitmap + 8); x++)
|
||||
{
|
||||
int lsn = x - lowDataLsnBitmap + 1;
|
||||
|
||||
if(getMessage().get(x))
|
||||
{
|
||||
if(pointer <= 64)
|
||||
{
|
||||
radios[lsn] = getMessage().getInt(TWO_BYTES, pointer);
|
||||
pointer += 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
radios[lsn] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!hasIdentifier)
|
||||
if(getActiveIdentifierMap().isEmpty())
|
||||
{
|
||||
sb.append("IDLE LSN: 1-16");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("ACTIVE LSN");
|
||||
|
||||
for(int x = 1; x < 17; x++)
|
||||
{
|
||||
if(talkgroups[x] != 0 || radios[x] != 0)
|
||||
{
|
||||
sb.append(" ").append(x);
|
||||
sb.append(" ").append(x);
|
||||
|
||||
if(talkgroups[x] == 0)
|
||||
if(mActiveIdentifierMap.containsKey(x))
|
||||
{
|
||||
Identifier id = mActiveIdentifierMap.get(x);
|
||||
|
||||
if(id instanceof TalkgroupIdentifier talkgroup)
|
||||
{
|
||||
if(radios[x] < 0)
|
||||
if(talkgroup.getValue() > 0)
|
||||
{
|
||||
sb.append(":(R)A"); //Radio active on channel but identifier is in a continuation message
|
||||
}
|
||||
else if(radios[x] > 0)
|
||||
{
|
||||
sb.append(":(R)").append(radios[x]); //Active radio ID for channel
|
||||
sb.append(":(T)").append(talkgroup.getValue()); //Active talkgroup ID for channel.
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(":"); //No radio or talkgroup active on channel
|
||||
sb.append(":(T)A"); //Talkgroup active on channel but identifier is in a continuation message
|
||||
}
|
||||
}
|
||||
else if(talkgroups[x] < 0)
|
||||
else if(id instanceof RadioIdentifier radio)
|
||||
{
|
||||
sb.append(":(T)A"); //Talkgroup active on channel but identifier is in a continuation message
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(":(T)").append(talkgroups[x]); //Active talkgroup ID for channel.
|
||||
if(radio.getValue() > 0)
|
||||
{
|
||||
sb.append(":(R)").append(radio.getValue()); //Active radio ID for channel
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(":(R)A"); //Radio active on channel but identifier is in a continuation message
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(":"); //No radio or talkgroup active on channel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this message has voice talkgroup activity.
|
||||
*
|
||||
* @return true if voice activity.
|
||||
*/
|
||||
public boolean hasVoiceTalkgroups()
|
||||
{
|
||||
return getSegmentIndicator().isFirst() && getMessage().getInt(LSN_VOICE_BITMAP) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of active LSNs.
|
||||
* @return map of active LSNs
|
||||
*/
|
||||
public Map<Integer,DMRLsn> getActiveLsnMap()
|
||||
{
|
||||
if(mActiveLsnMap == null)
|
||||
{
|
||||
getActiveIdentifierMap();
|
||||
}
|
||||
|
||||
return mActiveLsnMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of active voice talkgroups and DMR logical slots where they are active.
|
||||
*/
|
||||
public Map<Integer,IntegerIdentifier> getActiveIdentifierMap()
|
||||
{
|
||||
if(mActiveIdentifierMap == null)
|
||||
{
|
||||
mActiveIdentifierMap = new HashMap<>();
|
||||
mActiveLsnMap = new HashMap<>();
|
||||
|
||||
int pointer = LSN_1_8_BITMAP_START;
|
||||
|
||||
//Process voice LSNs 1-8
|
||||
if(getMessage().getInt(BYTE, pointer) > 0)
|
||||
{
|
||||
int lowLsnBitmap = pointer;
|
||||
pointer += 8;
|
||||
|
||||
for(int x = lowLsnBitmap; x < (lowLsnBitmap + 8); x++)
|
||||
{
|
||||
int lsn = x - lowLsnBitmap + 1;
|
||||
|
||||
if(getMessage().get(x))
|
||||
{
|
||||
if(pointer <= 72)
|
||||
{
|
||||
mActiveLsnMap.put(lsn, new DMRLsn(lsn));
|
||||
mActiveIdentifierMap.put(lsn, DMRTalkgroup.create(getMessage().getInt(BYTE, pointer)));
|
||||
pointer += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Create a placeholder for the talkgroup since we don't know the value
|
||||
mActiveLsnMap.put(lsn, new DMRLsn(lsn));
|
||||
mActiveIdentifierMap.put(lsn, DMRTalkgroup.create(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pointer += 8;
|
||||
}
|
||||
|
||||
//Process voice LSNs 9 - 16
|
||||
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
|
||||
{
|
||||
int highLsnBitmap = pointer;
|
||||
|
||||
pointer += 8;
|
||||
|
||||
for(int x = highLsnBitmap; x < (highLsnBitmap + 8); x++)
|
||||
{
|
||||
int lsn = x - highLsnBitmap + 1 + 8;
|
||||
|
||||
if(getMessage().get(x))
|
||||
{
|
||||
if(pointer <= 72)
|
||||
{
|
||||
mActiveLsnMap.put(lsn, new DMRLsn(lsn));
|
||||
mActiveIdentifierMap.put(lsn, DMRTalkgroup.create(getMessage().getInt(BYTE, pointer)));
|
||||
pointer += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Create a placeholder for the talkgroup since we don't know the value
|
||||
mActiveLsnMap.put(lsn, new DMRLsn(lsn));
|
||||
mActiveIdentifierMap.put(lsn, DMRTalkgroup.create(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pointer += 8;
|
||||
}
|
||||
|
||||
//Process Radio IDs - first bit in radio options byte is set to indicate more activity
|
||||
if(pointer <= 72 && getMessage().get(pointer))
|
||||
{
|
||||
pointer += 8;
|
||||
|
||||
//If we have the data revert channel bitmap ...
|
||||
if(pointer <= 72 && getMessage().getInt(BYTE, pointer) > 0)
|
||||
{
|
||||
int lowDataLsnBitmap = pointer;
|
||||
|
||||
pointer += 8;
|
||||
|
||||
for(int x = lowDataLsnBitmap; x < (lowDataLsnBitmap + 8); x++)
|
||||
{
|
||||
int lsn = x - lowDataLsnBitmap + 1;
|
||||
|
||||
if(getMessage().get(x))
|
||||
{
|
||||
if(pointer <= 64)
|
||||
{
|
||||
mActiveLsnMap.put(lsn, new DMRLsn(lsn));
|
||||
mActiveIdentifierMap.put(lsn, DMRRadio.createTo(getMessage().getInt(TWO_BYTES, pointer)));
|
||||
pointer += 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Create a placeholder for the radio since we don't know the value
|
||||
mActiveLsnMap.put(lsn, new DMRLsn(lsn));
|
||||
mActiveIdentifierMap.put(lsn, DMRRadio.createTo(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mActiveIdentifierMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Segment indicator for system status message values that are fragmented across multiple system status
|
||||
@@ -301,7 +343,15 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
@Override
|
||||
public int[] getLogicalChannelNumbers()
|
||||
{
|
||||
return getRestChannel().getLogicalChannelNumbers();
|
||||
Set<Integer> lcnSet = new HashSet<>();
|
||||
lcnSet.add(getRestChannel().getChannelNumber());
|
||||
|
||||
for(DMRLsn lsn: getActiveLsnMap().values())
|
||||
{
|
||||
lcnSet.add(lsn.getChannelNumber());
|
||||
}
|
||||
|
||||
return lcnSet.stream().mapToInt(v -> v).toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,6 +363,10 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
|
||||
public void apply(List<TimeslotFrequency> timeslotFrequencies)
|
||||
{
|
||||
getRestChannel().apply(timeslotFrequencies);
|
||||
for(DMRLsn lsn: getActiveLsnMap().values())
|
||||
{
|
||||
lsn.apply(timeslotFrequencies);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -151,6 +151,10 @@ public class AdjacentSiteInformation extends Announcement
|
||||
}
|
||||
|
||||
sb.append("CC:").append(getSlotType().getColorCode());
|
||||
if(hasRAS())
|
||||
{
|
||||
sb.append(" RAS:").append(getBPTCReservedBits());
|
||||
}
|
||||
sb.append(" ").append(getSystemIdentityCode().getModel());
|
||||
sb.append(" NEIGHBOR NETWORK:").append(getNeighborSystemIdentityCode().getNetwork());
|
||||
sb.append(" SITE:").append(getNeighborSystemIdentityCode().getSite());
|
||||
|
||||
@@ -105,6 +105,7 @@ public class TalkgroupVoiceChannelGrant extends ChannelGrant
|
||||
sb.append(" TALKGROUP VOICE CHANNEL GRANT FM:").append(getSourceRadio());
|
||||
sb.append(" TO:").append(getDestinationTalkgroup());
|
||||
sb.append(" ").append(getChannel());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnknownFullLCMe
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraTerminator;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAlias;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxTalkerAliasContinuation;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityMaxVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
|
||||
@@ -137,6 +140,15 @@ public class LCMessageFactory
|
||||
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
|
||||
flc = new MotorolaGroupVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER:
|
||||
flc = new CapacityMaxVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS:
|
||||
flc = new CapacityMaxTalkerAlias(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION:
|
||||
flc = new CapacityMaxTalkerAliasContinuation(message, timestamp, timeslot);
|
||||
break;
|
||||
case FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER:
|
||||
flc = new CapacityPlusEncryptedVoiceChannelUser(message, timestamp, timeslot);
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -45,6 +45,10 @@ public enum LCOpcode
|
||||
|
||||
FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 0, "GROUP VOICE CHANNEL USER"),
|
||||
FULL_CAPACITY_PLUS_WIDE_AREA_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 4, "WAN GROUP VOICE CHANNEL USER"),
|
||||
|
||||
FULL_CAPACITY_MAX_GROUP_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 16, "CAPMAX GROUP VOICE CHANNEL USER"),
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS(Vendor.MOTOROLA_CAPACITY_PLUS, true, 20, "CAPMAX TALKER ALIAS"),
|
||||
FULL_CAPACITY_MAX_TALKER_ALIAS_CONTINUATION(Vendor.MOTOROLA_CAPACITY_PLUS, true, 21, "CAPMAX TALKER ALIAS CONTINUATION"),
|
||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||
FULL_CAPACITY_PLUS_ENCRYPTED_VOICE_CHANNEL_USER(Vendor.MOTOROLA_CAPACITY_PLUS, true, 32, "ENCRYPTED VOICE CHANNEL USER"),
|
||||
//Observed on Cap+ Multi-Site System during an encrypted voice call
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Capacity Max Talker Alias (FLCO 20 / FID 0x10)
|
||||
* <p>
|
||||
* See also: FLCO 21 - Talker Alias Continuation
|
||||
*/
|
||||
public class CapacityMaxTalkerAlias extends CapacityPlusVoiceChannelUser
|
||||
{
|
||||
private static final int[] ENCODING_TYPE = new int[]{16, 17, 18};
|
||||
private static final int[] LENGTH = new int[]{19, 20, 21, 22};
|
||||
private static final int ALIAS_START = 24;
|
||||
private static final int ALIAS_END = ALIAS_START + (6 * 8); //6 characters?
|
||||
private DmrTalkerAliasIdentifier mTalkerAliasIdentifier;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param message for the link control payload
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxTalkerAlias(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
super(message, timestamp, timeslot);
|
||||
}
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
return getMessage().getInt(LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
if(isEncrypted())
|
||||
{
|
||||
sb.append(" *ENCRYPTED*");
|
||||
}
|
||||
if(isReservedBitSet())
|
||||
{
|
||||
sb.append(" *RESERVED-BIT*");
|
||||
}
|
||||
|
||||
sb.append("FLC MOTOROLA CAPMAX TALKER ALIAS:").append(getTalkerAliasIdentifier());
|
||||
sb.append(" LENGTH:").append(getLength());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Talker alias identifier
|
||||
*
|
||||
* @return identifier
|
||||
*/
|
||||
public DmrTalkerAliasIdentifier getTalkerAliasIdentifier()
|
||||
{
|
||||
if(mTalkerAliasIdentifier == null)
|
||||
{
|
||||
String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim();
|
||||
mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(alias);
|
||||
}
|
||||
|
||||
return mTalkerAliasIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.alias.DmrTalkerAliasIdentifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Capacity Max Talker Alias Continuation (FLCO 21 / FID 0x10)
|
||||
*/
|
||||
public class CapacityMaxTalkerAliasContinuation extends CapacityPlusVoiceChannelUser
|
||||
{
|
||||
private static final int ALIAS_START = 16;
|
||||
private static final int ALIAS_END = ALIAS_START + (7 * 8);
|
||||
private DmrTalkerAliasIdentifier mTalkerAliasIdentifier;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param message for the link control payload
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxTalkerAliasContinuation(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
super(message, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
if(isEncrypted())
|
||||
{
|
||||
sb.append(" *ENCRYPTED*");
|
||||
}
|
||||
if(isReservedBitSet())
|
||||
{
|
||||
sb.append(" *RESERVED-BIT*");
|
||||
}
|
||||
|
||||
sb.append("FLC MOTOROLA CAPMAX TALKER ALIAS CONTINUED:").append(getTalkerAliasIdentifier());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Talker alias identifier
|
||||
* @return identifier
|
||||
*/
|
||||
public DmrTalkerAliasIdentifier getTalkerAliasIdentifier()
|
||||
{
|
||||
if(mTalkerAliasIdentifier == null)
|
||||
{
|
||||
String alias = new String(getMessage().get(ALIAS_START, ALIAS_END).getBytes()).trim();
|
||||
mTalkerAliasIdentifier = DmrTalkerAliasIdentifier.create(alias);
|
||||
}
|
||||
|
||||
return mTalkerAliasIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkerAliasIdentifier());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRRadio;
|
||||
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Capacity Max Voice Channel User (FLCO 16 / 0x10)
|
||||
*/
|
||||
public class CapacityMaxVoiceChannelUser extends CapacityPlusVoiceChannelUser
|
||||
{
|
||||
private static final int[] TALKGROUP = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
private static final int[] RADIO = new int[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
|
||||
66, 67, 68, 69, 70, 71};
|
||||
|
||||
private TalkgroupIdentifier mTalkgroup;
|
||||
private RadioIdentifier mRadio;
|
||||
private List<Identifier> mIdentifiers;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param message for the link control payload
|
||||
* @param timestamp
|
||||
* @param timeslot
|
||||
*/
|
||||
public CapacityMaxVoiceChannelUser(CorrectedBinaryMessage message, long timestamp, int timeslot)
|
||||
{
|
||||
super(message, timestamp, timeslot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append("[CRC-ERROR] ");
|
||||
}
|
||||
if(isEncrypted())
|
||||
{
|
||||
sb.append(" *ENCRYPTED*");
|
||||
}
|
||||
if(isReservedBitSet())
|
||||
{
|
||||
sb.append(" *RESERVED-BIT*");
|
||||
}
|
||||
|
||||
sb.append("FLC MOTOROLA CAPMAX GROUP VOICE CHANNEL USER FM:");
|
||||
sb.append(getRadio());
|
||||
sb.append(" TO:").append(getTalkgroup());
|
||||
sb.append(" ").append(getServiceOptions());
|
||||
sb.append(" MSG:").append(getMessage().toHexString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Talkgroup TO ID
|
||||
*/
|
||||
public TalkgroupIdentifier getTalkgroup()
|
||||
{
|
||||
if(mTalkgroup == null)
|
||||
{
|
||||
mTalkgroup = DMRTalkgroup.create(getMessage().getInt(TALKGROUP));
|
||||
}
|
||||
|
||||
return mTalkgroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Radio FROM ID
|
||||
*/
|
||||
public RadioIdentifier getRadio()
|
||||
{
|
||||
if(mRadio == null)
|
||||
{
|
||||
mRadio = DMRRadio.createFrom(getMessage().getInt(RADIO));
|
||||
}
|
||||
|
||||
return mRadio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Identifier> getIdentifiers()
|
||||
{
|
||||
if(mIdentifiers == null)
|
||||
{
|
||||
mIdentifiers = new ArrayList<>();
|
||||
mIdentifiers.add(getTalkgroup());
|
||||
mIdentifiers.add(getRadio());
|
||||
}
|
||||
|
||||
return mIdentifiers;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,8 @@ public abstract class ShortLCMessage extends LCMessage implements IMessage
|
||||
{
|
||||
Vendor vendor = null;
|
||||
|
||||
if(binaryMessage.get(VENDOR_FLAG))
|
||||
//If the first bit is set, consider this a vendor variant, unless the opcode is 10 for Connect Plus Control Channel.
|
||||
if(binaryMessage.get(VENDOR_FLAG) && binaryMessage.getInt(OPCODE) != 10)
|
||||
{
|
||||
vendor = Vendor.fromValue(binaryMessage.getInt(VENDOR));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package io.github.dsheirer.module.decode.dmr.message.data.mbc;
|
||||
|
||||
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.CSBKMessageFactory;
|
||||
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
||||
@@ -34,21 +35,35 @@ public class MBCAssembler
|
||||
private MBCHeader mTS2Header;
|
||||
private List<MBCContinuationBlock> mTS1ContinuationBlocks = new ArrayList<>();
|
||||
private List<MBCContinuationBlock> mTS2ContinuationBlocks = new ArrayList<>();
|
||||
private boolean mIgnoreCrcChecksums;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public MBCAssembler()
|
||||
public MBCAssembler(boolean ignoreCrcChecksums)
|
||||
{
|
||||
mIgnoreCrcChecksums = ignoreCrcChecksums;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the message is valid or if the Ignore CRC Checksums feature is enabled.
|
||||
* @param message to check
|
||||
* @return true if ignore CRC checksums or if the message is valid.
|
||||
*/
|
||||
private boolean isValid(IMessage message)
|
||||
{
|
||||
return mIgnoreCrcChecksums || message.isValid();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Processes the MBC header
|
||||
* @param header to process
|
||||
*/
|
||||
public void process(MBCHeader header)
|
||||
{
|
||||
if(header != null && header.isValid())
|
||||
if(header != null && isValid(header))
|
||||
{
|
||||
reset(header.getTimeslot());
|
||||
|
||||
|
||||
@@ -148,6 +148,15 @@ public enum DecodeEventType
|
||||
DATA_CALLS.contains(this) || REGISTRATION.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this is a call event.
|
||||
* @return true if this is a call event type.
|
||||
*/
|
||||
public boolean isCallEvent()
|
||||
{
|
||||
return VOICE_CALLS.contains(this) || VOICE_CALLS_ENCRYPTED.contains(this) || DATA_CALLS.contains(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Label or pretty value for the element
|
||||
* @return label
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -53,20 +53,7 @@ public class Fleetsync2DecoderState extends DecoderState
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
public void init() {}
|
||||
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -150,20 +150,5 @@ public class LJ1200DecoderState extends DecoderState
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
public void init() {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -586,19 +586,7 @@ public class LTRNetDecoderState extends DecoderState
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
public void init() {}
|
||||
|
||||
/**
|
||||
* Resets the decoder state after a call or other decode event
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -153,19 +153,7 @@ public class LTRStandardDecoderState extends DecoderState
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
}
|
||||
public void init() {}
|
||||
|
||||
/**
|
||||
* Performs a temporal reset following a call or other decode event
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -58,22 +58,7 @@ public class MDCDecoderState extends DecoderState
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
public void init() {}
|
||||
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -220,6 +220,7 @@ public class MPT1327DecoderState extends DecoderState
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
//Send call start event for traffic channels to unsquelch the audio. Decoded return to channel message
|
||||
//or fade timeout expire will end the call event.
|
||||
if(mChannelType == ChannelType.TRAFFIC)
|
||||
@@ -228,18 +229,8 @@ public class MPT1327DecoderState extends DecoderState
|
||||
broadcast(new DecoderStateEvent(this, Event.START, State.CALL));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
public void init() {}
|
||||
|
||||
protected void resetState()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -301,6 +301,13 @@ public class MPT1327TrafficChannelManager extends TrafficChannelManager implemen
|
||||
*/
|
||||
public class TrafficChannelTeardownMonitor implements Listener<ChannelEvent>
|
||||
{
|
||||
|
||||
/**
|
||||
* Process channel events from the ChannelProcessingManager to account for owned child traffic channels.
|
||||
* Note: this method sees events for ALL channels and not just MPT1327 channels managed by this instance.
|
||||
*
|
||||
* @param channelEvent to process
|
||||
*/
|
||||
@Override
|
||||
public synchronized void receive(ChannelEvent channelEvent)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -309,6 +309,10 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
||||
broadcast(event);
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(frequency);
|
||||
if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner)
|
||||
{
|
||||
sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner());
|
||||
}
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedTrafficChannelMap.put(frequency, trafficChannel);
|
||||
|
||||
@@ -356,6 +360,10 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
||||
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(frequency);
|
||||
if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner)
|
||||
{
|
||||
sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner());
|
||||
}
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedTrafficChannelMap.put(frequency, trafficChannel);
|
||||
|
||||
@@ -455,6 +463,10 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
||||
broadcast(event);
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(frequency);
|
||||
if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner)
|
||||
{
|
||||
sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner());
|
||||
}
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedTrafficChannelMap.put(frequency, trafficChannel);
|
||||
|
||||
@@ -517,6 +529,10 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
||||
|
||||
SourceConfigTuner sourceConfig = new SourceConfigTuner();
|
||||
sourceConfig.setFrequency(frequency);
|
||||
if(mParentChannel.getSourceConfiguration() instanceof SourceConfigTuner parentConfigTuner)
|
||||
{
|
||||
sourceConfig.setPreferredTuner(parentConfigTuner.getPreferredTuner());
|
||||
}
|
||||
trafficChannel.setSourceConfiguration(sourceConfig);
|
||||
mAllocatedTrafficChannelMap.put(frequency, trafficChannel);
|
||||
|
||||
@@ -778,6 +794,12 @@ public class P25TrafficChannelManager extends TrafficChannelManager implements I
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process channel events from the ChannelProcessingManager to account for owned child traffic channels.
|
||||
* Note: this method sees events for ALL channels and not just P25 channels managed by this instance.
|
||||
*
|
||||
* @param channelEvent to process
|
||||
*/
|
||||
@Override
|
||||
public synchronized void receive(ChannelEvent channelEvent)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,9 @@ import io.github.dsheirer.audio.codec.mbe.MBECallSequenceRecorder;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HDUMessage;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.hdu.HeaderData;
|
||||
import io.github.dsheirer.identifier.encryption.EncryptionKeyIdentifier;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.LinkControlWord;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUpdate;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.lc.motorola.LCMotorolaPatchGroupVoiceChannelUser;
|
||||
@@ -124,6 +127,10 @@ public class P25P1CallSequenceRecorder extends MBECallSequenceRecorder
|
||||
{
|
||||
flush();
|
||||
}
|
||||
else if (message instanceof HDUMessage)
|
||||
{
|
||||
process((HDUMessage)message);
|
||||
}
|
||||
}
|
||||
|
||||
private void process(TDULinkControlMessage tdulc)
|
||||
@@ -228,4 +235,21 @@ public class P25P1CallSequenceRecorder extends MBECallSequenceRecorder
|
||||
mCallSequence.setEncryptionSyncParameters(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
private void process(HDUMessage hduMessage)
|
||||
{
|
||||
if(mCallSequence == null)
|
||||
{
|
||||
mCallSequence = new MBECallSequence(PROTOCOL);
|
||||
}
|
||||
|
||||
HeaderData hd = hduMessage.getHeaderData();
|
||||
|
||||
if (hd.isEncryptedAudio())
|
||||
{
|
||||
mCallSequence.setEncrypted(true);
|
||||
Phase2EncryptionSyncParameters esp = new Phase2EncryptionSyncParameters((EncryptionKeyIdentifier)hd.getEncryptionKey(), hd.getMessageIndicator());
|
||||
mCallSequence.setEncryptionSyncParameters(esp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.github.dsheirer.audio.codec.mbe.MBECallSequence;
|
||||
import io.github.dsheirer.audio.codec.mbe.MBECallSequenceRecorder;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.EncryptionSynchronizationSequence;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.P25P2Message;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacMessage;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.message.mac.MacStructure;
|
||||
@@ -153,6 +154,10 @@ public class P25P2CallSequenceRecorder extends MBECallSequenceRecorder
|
||||
{
|
||||
process((MacMessage)message);
|
||||
}
|
||||
else if (message instanceof EncryptionSynchronizationSequence)
|
||||
{
|
||||
processEss((EncryptionSynchronizationSequence)message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,5 +387,18 @@ public class P25P2CallSequenceRecorder extends MBECallSequenceRecorder
|
||||
mLog.warn("Expected push-to-talk structure but found: " + mac.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private void processEss(EncryptionSynchronizationSequence ess)
|
||||
{
|
||||
if(mCallSequence == null)
|
||||
{
|
||||
mCallSequence = new MBECallSequence(PROTOCOL);
|
||||
}
|
||||
if (ess.isEncrypted())
|
||||
{
|
||||
mCallSequence.setEncrypted(true);
|
||||
mCallSequence.setEncryptionSyncParameters(ess);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -1948,6 +1948,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
mPatchGroupManager.clear();
|
||||
|
||||
//Change the default (45-second) traffic channel timeout to 1 second
|
||||
@@ -1965,6 +1966,7 @@ public class P25P1DecoderState extends DecoderState implements IChannelEventList
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
super.stop();
|
||||
mPatchGroupManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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,23 +14,21 @@
|
||||
*
|
||||
* 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.ldu;
|
||||
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.message.P25Message;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* LDU voice frame
|
||||
*/
|
||||
public abstract class LDUMessage extends P25Message
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LDUMessage.class);
|
||||
|
||||
public static final int IMBE_FRAME_1 = 0;
|
||||
public static final int IMBE_FRAME_2 = 144;
|
||||
public static final int IMBE_FRAME_3 = 328;
|
||||
@@ -41,10 +38,16 @@ public abstract class LDUMessage extends P25Message
|
||||
public static final int IMBE_FRAME_7 = 1064;
|
||||
public static final int IMBE_FRAME_8 = 1248;
|
||||
public static final int IMBE_FRAME_9 = 1424;
|
||||
public static final int[] LOW_SPEED_DATA = {1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1408, 1409, 1410, 1411,
|
||||
1412, 1413, 1414, 1415};
|
||||
private List<byte[]> mIMBIFrames;
|
||||
|
||||
public static final int[] LOW_SPEED_DATA = {1392, 1393, 1394, 1395, 1396, 1397,
|
||||
1398, 1399, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415};
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param message with data
|
||||
* @param nac code
|
||||
* @param timestamp for the message
|
||||
*/
|
||||
public LDUMessage(CorrectedBinaryMessage message, int nac, long timestamp)
|
||||
{
|
||||
super(message, nac, timestamp);
|
||||
@@ -58,6 +61,10 @@ public abstract class LDUMessage extends P25Message
|
||||
return getMessage().getHex(LOW_SPEED_DATA, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base message used by subclass implementations.
|
||||
* @return base message
|
||||
*/
|
||||
public String getMessageStub()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -70,24 +77,25 @@ public abstract class LDUMessage extends P25Message
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a 162 byte array containing 9 IMBE voice frames of 18-bytes
|
||||
* (144-bits) each. Each frame is intact as transmitted and requires
|
||||
* deinterleaving, error correction, derandomizing, etc.
|
||||
* Returns a 162 byte array containing 9 IMBE voice frames of 18-bytes* (144-bits) each. Each frame is intact as
|
||||
* transmitted and requires deinterleaving, error correction, derandomizing, etc.
|
||||
*/
|
||||
public List<byte[]> getIMBEFrames()
|
||||
public synchronized List<byte[]> getIMBEFrames()
|
||||
{
|
||||
List<byte[]> frames = new ArrayList<byte[]>();
|
||||
if(mIMBIFrames == null)
|
||||
{
|
||||
mIMBIFrames = new ArrayList<>();
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_1, IMBE_FRAME_1 + 144).toByteArray());
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_2, IMBE_FRAME_2 + 144).toByteArray());
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_3, IMBE_FRAME_3 + 144).toByteArray());
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_4, IMBE_FRAME_4 + 144).toByteArray());
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_5, IMBE_FRAME_5 + 144).toByteArray());
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_6, IMBE_FRAME_6 + 144).toByteArray());
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_7, IMBE_FRAME_7 + 144).toByteArray());
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_8, IMBE_FRAME_8 + 144).toByteArray());
|
||||
mIMBIFrames.add(getMessage().get(IMBE_FRAME_9, IMBE_FRAME_9 + 144).toByteArray());
|
||||
}
|
||||
|
||||
frames.add(getMessage().get(IMBE_FRAME_1, IMBE_FRAME_1 + 144).toByteArray());
|
||||
frames.add(getMessage().get(IMBE_FRAME_2, IMBE_FRAME_2 + 144).toByteArray());
|
||||
frames.add(getMessage().get(IMBE_FRAME_3, IMBE_FRAME_3 + 144).toByteArray());
|
||||
frames.add(getMessage().get(IMBE_FRAME_4, IMBE_FRAME_4 + 144).toByteArray());
|
||||
frames.add(getMessage().get(IMBE_FRAME_5, IMBE_FRAME_5 + 144).toByteArray());
|
||||
frames.add(getMessage().get(IMBE_FRAME_6, IMBE_FRAME_6 + 144).toByteArray());
|
||||
frames.add(getMessage().get(IMBE_FRAME_7, IMBE_FRAME_7 + 144).toByteArray());
|
||||
frames.add(getMessage().get(IMBE_FRAME_8, IMBE_FRAME_8 + 144).toByteArray());
|
||||
frames.add(getMessage().get(IMBE_FRAME_9, IMBE_FRAME_9 + 144).toByteArray());
|
||||
|
||||
return frames;
|
||||
return mIMBIFrames;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -1161,6 +1161,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
super.start();
|
||||
mPatchGroupManager.clear();
|
||||
|
||||
//Change the default (45-second) traffic channel timeout to 1 second
|
||||
@@ -1178,6 +1179,7 @@ public class P25P2DecoderState extends TimeslotDecoderState implements Identifie
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
super.stop();
|
||||
mPatchGroupManager.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,11 +101,30 @@ public class P25P2MessageProcessor implements Listener<IMessage>
|
||||
mFrequencyBandMap.put(bandIdentifier.getIdentifier(), bandIdentifier);
|
||||
}
|
||||
|
||||
switch(macMessage.getMacPduType())
|
||||
{
|
||||
case MAC_1_PTT:
|
||||
case MAC_2_END_PTT:
|
||||
case MAC_6_HANGTIME:
|
||||
case MAC_3_IDLE:
|
||||
if (timeslot.getTimeslot() == 0)
|
||||
{
|
||||
mESSProcessor0.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
mESSProcessor1.reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
mMessageListener.receive(macMessage);
|
||||
}
|
||||
}
|
||||
else if(timeslot instanceof AbstractVoiceTimeslot)
|
||||
{
|
||||
mMessageListener.receive(timeslot);
|
||||
|
||||
if(timeslot.getTimeslot() == 0)
|
||||
{
|
||||
mESSProcessor0.process((AbstractVoiceTimeslot)timeslot);
|
||||
@@ -139,8 +158,6 @@ public class P25P2MessageProcessor implements Listener<IMessage>
|
||||
}
|
||||
}
|
||||
|
||||
mMessageListener.receive(timeslot);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -78,9 +78,21 @@ public class L3HarrisTalkerAlias extends MacStructure
|
||||
{
|
||||
if(mAliasIdentifier == null)
|
||||
{
|
||||
int length = getLength() * 8;
|
||||
String alias = new String(getMessage().getSubMessage(ALIAS_START + getOffset(), length + getOffset()).getBytes()).trim();
|
||||
mAliasIdentifier = P25TalkerAliasIdentifier.create(alias);
|
||||
int length = getLength();
|
||||
|
||||
if(length > 0)
|
||||
{
|
||||
length *= 8;
|
||||
length += getOffset();
|
||||
|
||||
if(length > getMessage().size())
|
||||
{
|
||||
length = getMessage().size();
|
||||
}
|
||||
|
||||
String alias = new String(getMessage().getSubMessage(ALIAS_START + getOffset(), length).getBytes()).trim();
|
||||
mAliasIdentifier = P25TalkerAliasIdentifier.create(alias);
|
||||
}
|
||||
}
|
||||
|
||||
return mAliasIdentifier;
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 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.p25.phase2.timeslot;
|
||||
@@ -25,7 +22,6 @@ package io.github.dsheirer.module.decode.p25.phase2.timeslot;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -61,7 +57,7 @@ public class Voice2Timeslot extends AbstractVoiceTimeslot
|
||||
/**
|
||||
* Voice frames contained in this timeslot
|
||||
*/
|
||||
public List<BinaryMessage> getVoiceFrames()
|
||||
public synchronized List<BinaryMessage> getVoiceFrames()
|
||||
{
|
||||
if(mVoiceFrames == null)
|
||||
{
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 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.p25.phase2.timeslot;
|
||||
@@ -25,7 +22,6 @@ package io.github.dsheirer.module.decode.p25.phase2.timeslot;
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.enumeration.DataUnitID;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -62,7 +58,7 @@ public class Voice4Timeslot extends AbstractVoiceTimeslot
|
||||
/**
|
||||
* Voice frames contained in this timeslot
|
||||
*/
|
||||
public List<BinaryMessage> getVoiceFrames()
|
||||
public synchronized List<BinaryMessage> getVoiceFrames()
|
||||
{
|
||||
if(mVoiceFrames == null)
|
||||
{
|
||||
@@ -71,7 +67,6 @@ public class Voice4Timeslot extends AbstractVoiceTimeslot
|
||||
mVoiceFrames.add(getMessage().getSubMessage(FRAME_2_START, FRAME_2_START + FRAME_LENGTH));
|
||||
mVoiceFrames.add(getMessage().getSubMessage(FRAME_3_START, FRAME_3_START + FRAME_LENGTH));
|
||||
mVoiceFrames.add(getMessage().getSubMessage(FRAME_4_START, FRAME_4_START + FRAME_LENGTH));
|
||||
return mVoiceFrames;
|
||||
}
|
||||
|
||||
return mVoiceFrames;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -327,22 +327,7 @@ public class PassportDecoderState extends DecoderState
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
public void init() {}
|
||||
|
||||
protected void resetState()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -58,22 +58,7 @@ public class Tait1200DecoderState extends DecoderState
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init()
|
||||
{
|
||||
|
||||
}
|
||||
public void init() {}
|
||||
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
|
||||
325
src/main/java/io/github/dsheirer/monitor/DiagnosticMonitor.java
Normal file
325
src/main/java/io/github/dsheirer/monitor/DiagnosticMonitor.java
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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.monitor;
|
||||
|
||||
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
|
||||
import io.github.dsheirer.log.LoggingSuppressor;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import io.github.dsheirer.util.TimeStamp;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
/**
|
||||
* Utility class for monitoring system components and producing logging reports.
|
||||
*/
|
||||
public class DiagnosticMonitor
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(DiagnosticMonitor.class);
|
||||
private final LoggingSuppressor LOG_SUPPRESSOR = new LoggingSuppressor(LOGGER);
|
||||
private static final String DIVIDER = "\n\n=========================================================================\n\n";
|
||||
private UserPreferences mUserPreferences;
|
||||
private ChannelProcessingManager mChannelProcessingManager;
|
||||
private TunerManager mTunerManager;
|
||||
private ScheduledFuture<?> mBlockedThreadMonitorHandle;
|
||||
private BlockedThreadMonitor mMonitor = new BlockedThreadMonitor();
|
||||
private boolean mUserAlertedToBlockedThreadCondition = false;
|
||||
private Map<Integer,Integer> mBlockedThreadDetectionCountMap = new HashMap<>();
|
||||
private boolean mHeadless;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for application logging directory lookup.
|
||||
* @param channelProcessingManager for accessing running channel information
|
||||
* @param tunerManager for accessing allocated tuner channel information
|
||||
* @param headless to indicate if the thread deadlock monitor should show a user notification.
|
||||
*/
|
||||
public DiagnosticMonitor(UserPreferences userPreferences, ChannelProcessingManager channelProcessingManager,
|
||||
TunerManager tunerManager, boolean headless)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
mChannelProcessingManager = channelProcessingManager;
|
||||
mTunerManager = tunerManager;
|
||||
mHeadless = headless;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts monitoring for blocked threads
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
if(mBlockedThreadMonitorHandle != null)
|
||||
{
|
||||
mBlockedThreadMonitorHandle.cancel(true);
|
||||
}
|
||||
|
||||
if(mUserPreferences.getApplicationPreference().isAutomaticDiagnosticMonitoring())
|
||||
{
|
||||
LOGGER.info("Diagnostic monitoring enabled running every 30 seconds");
|
||||
mBlockedThreadMonitorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(mMonitor, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.info("Diagnostic monitoring disabled per user preference (application).");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops monitoring for blocked threads.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
if(mBlockedThreadMonitorHandle != null)
|
||||
{
|
||||
mBlockedThreadMonitorHandle.cancel(true);
|
||||
}
|
||||
|
||||
mBlockedThreadMonitorHandle = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for blocked threads and on discovery, generates a diagnostic report and notifies the user (once).
|
||||
*/
|
||||
private void checkForBlockedThreads()
|
||||
{
|
||||
if(!mUserAlertedToBlockedThreadCondition)
|
||||
{
|
||||
try
|
||||
{
|
||||
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
|
||||
|
||||
long ids[] = bean.findDeadlockedThreads();
|
||||
|
||||
if(ids != null)
|
||||
{
|
||||
mUserAlertedToBlockedThreadCondition = true;
|
||||
|
||||
ThreadInfo threadInfo[] = bean.getThreadInfo(ids);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("sdrtrunk detected a critical application error with a threading deadlock, described as follows:\n");
|
||||
|
||||
for (ThreadInfo threadInfo1 : threadInfo)
|
||||
{
|
||||
sb.append("Thread ID[").append(threadInfo1.getThreadId());
|
||||
sb.append("] Name [").append(threadInfo1.getThreadName());
|
||||
sb.append("] Lock [").append(threadInfo1.getLockName());
|
||||
sb.append("] Owned By [ID:").append(threadInfo1.getLockOwnerId());
|
||||
sb.append(" | NAME:").append(threadInfo1.getLockName());
|
||||
sb.append("]\n");
|
||||
}
|
||||
|
||||
LOGGER.error(sb.toString());
|
||||
Path reportPath = generateProcessingDiagnosticReport(sb + DIVIDER);
|
||||
LOGGER.error("Thread deadlock report generated: " + reportPath);
|
||||
|
||||
if(!mHeadless)
|
||||
{
|
||||
String title = "sdrtrunk: Critical Error Detected";
|
||||
String message = "The sdrtrunk application has detected a thread deadlock situation.\n" +
|
||||
"The application may degrade over time and eventually run out of memory.\n" +
|
||||
"A diagnostic report was generated. Please open an issue on the GitHub\n" +
|
||||
"website and attach this diagnostic report:\n\n" + reportPath.toString();
|
||||
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG_SUPPRESSOR.error("run error", 1, "Error while monitoring for deadlocked " +
|
||||
"threads: " + e.getLocalizedMessage());
|
||||
//Set the flag so that we don't try to run again.
|
||||
mUserAlertedToBlockedThreadCondition = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a diagnostic report containing state information for channels that are in a processing state.
|
||||
* @param message to prepend to the report
|
||||
* @return path for the log file that was created.
|
||||
*/
|
||||
public Path generateProcessingDiagnosticReport(String message) throws IOException
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(message);
|
||||
sb.append("\n\nsdrtrunk Processing Diagnostic Report\n");
|
||||
sb.append(DIVIDER);
|
||||
sb.append(getEnvironmentReport());
|
||||
sb.append(DIVIDER);
|
||||
sb.append(mTunerManager.getDiscoveredTunerModel().getDiagnosticReport());
|
||||
sb.append(DIVIDER);
|
||||
sb.append(mChannelProcessingManager.getDiagnosticInformation());
|
||||
sb.append(DIVIDER);
|
||||
sb.append(mChannelProcessingManager.getChannelMetadataModel().getDiagnosticInformation());
|
||||
sb.append(DIVIDER);
|
||||
sb.append(getThreadDumpReport());
|
||||
sb.append(DIVIDER);
|
||||
|
||||
Path logDirectory = mUserPreferences.getDirectoryPreference().getDirectoryApplicationLog();
|
||||
String file = TimeStamp.getFileFormattedDateTime() + "_sdrtrunk_processing_diagnostic_report.log";
|
||||
Path output = logDirectory.resolve(file);
|
||||
Files.write(output, sb.toString().getBytes());
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the current threads to a log file with current date and time to the application log directory.
|
||||
* @return path to the thread dump log file that was created.
|
||||
* @throws IOException if there is an issue writing the contents to the log file.
|
||||
*/
|
||||
public Path generateThreadDumpReport() throws IOException
|
||||
{
|
||||
String report = getThreadDumpReport();
|
||||
Path logDirectory = mUserPreferences.getDirectoryPreference().getDirectoryApplicationLog();
|
||||
String file = TimeStamp.getFileFormattedDateTime() + "_sdrtrunk_thread_dump.log";
|
||||
Path output = logDirectory.resolve(file);
|
||||
Files.write(output, report.getBytes());
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a thread dump report.
|
||||
* @return report text.
|
||||
*/
|
||||
public String getThreadDumpReport()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Thread Dump Report\n\n");
|
||||
|
||||
for(Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet())
|
||||
{
|
||||
sb.append(entry.getKey() + " " + entry.getKey().getState()).append("\n");
|
||||
|
||||
for(StackTraceElement ste : entry.getValue())
|
||||
{
|
||||
sb.append("\tat " + ste).append("\n");
|
||||
}
|
||||
|
||||
sb.append("\n\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a JVM and application environment report
|
||||
*/
|
||||
public String getEnvironmentReport()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("JVM and Application Environment Report\n");
|
||||
Attributes atts = findManifestAttributes();
|
||||
if (atts != null) {
|
||||
sb.append("\nVersion : " + atts.getValue("Implementation-Version"));
|
||||
sb.append("\nGradle Version : " + atts.getValue("Created-By"));
|
||||
sb.append("\nBuild Timestamp : " + atts.getValue("Build-Timestamp"));
|
||||
sb.append("\nBuild-JDK : " + atts.getValue("Build-JDK"));
|
||||
sb.append("\nBuild OS : " + atts.getValue("Build-OS"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("\nApplication: no build information available");
|
||||
}
|
||||
|
||||
sb.append("\nHost OS Name: " + System.getProperty("os.name"));
|
||||
sb.append("\nHost OS Arch: " + System.getProperty("os.arch"));
|
||||
sb.append("\nHost OS Version: " + System.getProperty("os.version"));
|
||||
sb.append("\nHost CPU Cores: " + Runtime.getRuntime().availableProcessors());
|
||||
sb.append("\nHost Max Java Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
|
||||
sb.append("\nHost Allocated Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().totalMemory()));
|
||||
sb.append("\nHost Free Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().freeMemory()));
|
||||
sb.append("\nHost Used Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().totalMemory() -
|
||||
Runtime.getRuntime().freeMemory()));
|
||||
sb.append("\nStorage Directories:");
|
||||
sb.append("\n Application Root: " + mUserPreferences.getDirectoryPreference().getDirectoryApplicationRoot());
|
||||
sb.append("\n Application Log: " + mUserPreferences.getDirectoryPreference().getDirectoryApplicationLog());
|
||||
sb.append("\n Event Log: " + mUserPreferences.getDirectoryPreference().getDirectoryEventLog());
|
||||
sb.append("\n Playlist: " + mUserPreferences.getDirectoryPreference().getDirectoryPlaylist());
|
||||
sb.append("\n Recordings: " + mUserPreferences.getDirectoryPreference().getDirectoryRecording());
|
||||
sb.append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the jar manifest attributes
|
||||
* @return attributes or null.
|
||||
*/
|
||||
public Attributes findManifestAttributes() {
|
||||
try {
|
||||
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
try {
|
||||
Manifest manifest = new Manifest(resources.nextElement().openStream());
|
||||
Attributes atts = manifest.getMainAttributes();
|
||||
Boolean hasTitle = atts.containsValue("sdrtrunk project");
|
||||
if (hasTitle) {
|
||||
return atts;
|
||||
}
|
||||
} catch (IOException E) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable to periodically check for blocked threads
|
||||
*/
|
||||
public class BlockedThreadMonitor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
checkForBlockedThreads();
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
LOG_SUPPRESSOR.error("Error", 3, "Error while checking for blocked threads", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import io.github.dsheirer.preference.application.ApplicationPreference;
|
||||
import io.github.dsheirer.preference.calibration.VectorCalibrationPreference;
|
||||
import io.github.dsheirer.preference.decoder.JmbeLibraryPreference;
|
||||
import io.github.dsheirer.preference.directory.DirectoryPreference;
|
||||
import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
|
||||
import io.github.dsheirer.preference.duplicate.CallManagementPreference;
|
||||
import io.github.dsheirer.preference.event.DecodeEventPreference;
|
||||
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
|
||||
import io.github.dsheirer.preference.javafx.JavaFxPreferences;
|
||||
@@ -60,7 +60,7 @@ public class UserPreferences implements Listener<PreferenceType>
|
||||
private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference;
|
||||
private DecodeEventPreference mDecodeEventPreference;
|
||||
private DirectoryPreference mDirectoryPreference;
|
||||
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
|
||||
private CallManagementPreference mDuplicateCallDetectionPreference;
|
||||
private JmbeLibraryPreference mJmbeLibraryPreference;
|
||||
private MP3Preference mMP3Preference;
|
||||
private PlaybackPreference mPlaybackPreference;
|
||||
@@ -208,7 +208,7 @@ public class UserPreferences implements Listener<PreferenceType>
|
||||
/**
|
||||
* Duplicate call detection preferences
|
||||
*/
|
||||
public DuplicateCallDetectionPreference getDuplicateCallDetectionPreference()
|
||||
public CallManagementPreference getDuplicateCallDetectionPreference()
|
||||
{
|
||||
return mDuplicateCallDetectionPreference;
|
||||
}
|
||||
@@ -222,7 +222,7 @@ public class UserPreferences implements Listener<PreferenceType>
|
||||
mChannelMultiFrequencyPreference = new ChannelMultiFrequencyPreference(this::receive);
|
||||
mDecodeEventPreference = new DecodeEventPreference(this::receive);
|
||||
mDirectoryPreference = new DirectoryPreference(this::receive);
|
||||
mDuplicateCallDetectionPreference = new DuplicateCallDetectionPreference(this::receive);
|
||||
mDuplicateCallDetectionPreference = new CallManagementPreference(this::receive);
|
||||
mJmbeLibraryPreference = new JmbeLibraryPreference(this::receive);
|
||||
mMP3Preference = new MP3Preference(this::receive);
|
||||
mPlaybackPreference = new PlaybackPreference(this::receive);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -31,11 +31,13 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class ApplicationPreference extends Preference
|
||||
{
|
||||
private static final String PREFERENCE_KEY_CHANNEL_AUTO_DIAGNOSTIC_MONITORING = "automatic.diagnostic.monitoring";
|
||||
private static final String PREFERENCE_KEY_CHANNEL_AUTO_START_TIMEOUT = "channel.auto.start.timeout";
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ApplicationPreference.class);
|
||||
private Preferences mPreferences = Preferences.userNodeForPackage(ApplicationPreference.class);
|
||||
private Integer mChannelAutoStartTimeout;
|
||||
private Boolean mAutomaticDiagnosticMonitoring;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -77,4 +79,29 @@ public class ApplicationPreference extends Preference
|
||||
mPreferences.putInt(PREFERENCE_KEY_CHANNEL_AUTO_START_TIMEOUT, timeout);
|
||||
notifyPreferenceUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if automatic diagnostic monitoring is enabled.
|
||||
* @return enabled.
|
||||
*/
|
||||
public boolean isAutomaticDiagnosticMonitoring()
|
||||
{
|
||||
if(mAutomaticDiagnosticMonitoring == null)
|
||||
{
|
||||
mAutomaticDiagnosticMonitoring = mPreferences.getBoolean(PREFERENCE_KEY_CHANNEL_AUTO_DIAGNOSTIC_MONITORING, true);
|
||||
}
|
||||
|
||||
return mAutomaticDiagnosticMonitoring;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enabled state for automatic diagnostic monitoring.
|
||||
* @param enabled true to turn on monitoring.
|
||||
*/
|
||||
public void setAutomaticDiagnosticMonitoring(boolean enabled)
|
||||
{
|
||||
mAutomaticDiagnosticMonitoring = enabled;
|
||||
mPreferences.putBoolean(PREFERENCE_KEY_CHANNEL_AUTO_DIAGNOSTIC_MONITORING, enabled);
|
||||
notifyPreferenceUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -19,38 +19,40 @@
|
||||
|
||||
package io.github.dsheirer.preference.duplicate;
|
||||
|
||||
import io.github.dsheirer.audio.broadcast.PatchGroupStreamingOption;
|
||||
import io.github.dsheirer.preference.Preference;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.prefs.Preferences;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* User preferences for duplicate call detection
|
||||
* User preferences for call management
|
||||
*/
|
||||
public class DuplicateCallDetectionPreference extends Preference
|
||||
public class CallManagementPreference extends Preference
|
||||
{
|
||||
private static final String PREFERENCE_KEY_DETECT_DUPLICATE_TALKGROUP = "duplicate.call.detect.talkgroup";
|
||||
private static final String PREFERENCE_KEY_DETECT_DUPLICATE_RADIO = "duplicate.call.detect.radio";
|
||||
private static final String PREFERENCE_KEY_SUPPRESS_DUPLICATE_PLAYBACK = "suppress.duplicate.audio.playback";
|
||||
private static final String PREFERENCE_KEY_SUPPRESS_DUPLICATE_RECORDING = "suppress.duplicate.audio.recording";
|
||||
private static final String PREFERENCE_KEY_SUPPRESS_DUPLICATE_STREAMING = "suppress.duplicate.audio.streaming";
|
||||
private static final String PREFERENCE_KEY_PATCHGROUP_STREAMING = "patchgroup.streaming";
|
||||
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DuplicateCallDetectionPreference.class);
|
||||
private Preferences mPreferences = Preferences.userNodeForPackage(DuplicateCallDetectionPreference.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(CallManagementPreference.class);
|
||||
private Preferences mPreferences = Preferences.userNodeForPackage(CallManagementPreference.class);
|
||||
private Boolean mDuplicateCallDetectionByTalkgroupEnabled;
|
||||
private Boolean mDuplicateCallDetectionByRadioEnabled;
|
||||
private Boolean mDuplicatePlaybackSuppressionEnabled;
|
||||
private Boolean mDuplicateRecordingSuppressionEnabled;
|
||||
private Boolean mDuplicateStreamingSuppressionEnabled;
|
||||
private PatchGroupStreamingOption mPatchGroupStreamingOption;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param updateListener to receive notifications that a preference has been updated
|
||||
*/
|
||||
public DuplicateCallDetectionPreference(Listener<PreferenceType> updateListener)
|
||||
public CallManagementPreference(Listener<PreferenceType> updateListener)
|
||||
{
|
||||
super(updateListener);
|
||||
}
|
||||
@@ -182,6 +184,51 @@ public class DuplicateCallDetectionPreference extends Preference
|
||||
{
|
||||
mPreferences.putBoolean(PREFERENCE_KEY_SUPPRESS_DUPLICATE_STREAMING, enabled);
|
||||
mDuplicateStreamingSuppressionEnabled = enabled;
|
||||
notifyPreferenceUpdated();;
|
||||
notifyPreferenceUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch group streaming option preference.
|
||||
* @return option.
|
||||
*/
|
||||
public PatchGroupStreamingOption getPatchGroupStreamingOption()
|
||||
{
|
||||
if(mPatchGroupStreamingOption == null)
|
||||
{
|
||||
String option = mPreferences.get(PREFERENCE_KEY_PATCHGROUP_STREAMING, PatchGroupStreamingOption.PATCH_GROUP.name());
|
||||
|
||||
if(option != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
mPatchGroupStreamingOption = PatchGroupStreamingOption.valueOf(option);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Ignore ... we'll use the default value.
|
||||
}
|
||||
}
|
||||
|
||||
if(mPatchGroupStreamingOption == null)
|
||||
{
|
||||
mPatchGroupStreamingOption = PatchGroupStreamingOption.PATCH_GROUP;
|
||||
}
|
||||
}
|
||||
|
||||
return mPatchGroupStreamingOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the patch group streaming option preference.
|
||||
* @param patchGroupStreamingOption preference
|
||||
*/
|
||||
public void setPatchGroupStreamingOption(PatchGroupStreamingOption patchGroupStreamingOption)
|
||||
{
|
||||
if(patchGroupStreamingOption != null)
|
||||
{
|
||||
mPreferences.put(PREFERENCE_KEY_PATCHGROUP_STREAMING, patchGroupStreamingOption.name());
|
||||
mPatchGroupStreamingOption = patchGroupStreamingOption;
|
||||
notifyPreferenceUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -24,6 +24,7 @@ import io.github.dsheirer.audio.AudioSegment;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.wave.AudioMetadata;
|
||||
import io.github.dsheirer.record.wave.AudioMetadataUtils;
|
||||
@@ -58,14 +59,28 @@ public class AudioSegmentRecorder
|
||||
*/
|
||||
public static void record(AudioSegment audioSegment, Path path, RecordFormat recordFormat,
|
||||
UserPreferences userPreferences) throws IOException
|
||||
{
|
||||
record(audioSegment, path, recordFormat, userPreferences, audioSegment.getIdentifierCollection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the audio segment to the specified path using the specified recording format
|
||||
* @param audioSegment to record
|
||||
* @param path for the recording
|
||||
* @param recordFormat to use (WAVE, MP3)
|
||||
* @param identifierCollection to use instead of the audioSegment's embedded collection.
|
||||
* @throws IOException on any errors
|
||||
*/
|
||||
public static void record(AudioSegment audioSegment, Path path, RecordFormat recordFormat,
|
||||
UserPreferences userPreferences, IdentifierCollection identifierCollection) throws IOException
|
||||
{
|
||||
switch(recordFormat)
|
||||
{
|
||||
case MP3:
|
||||
recordMP3(audioSegment, path, userPreferences);
|
||||
recordMP3(audioSegment, path, userPreferences, identifierCollection);
|
||||
break;
|
||||
case WAVE:
|
||||
recordWAVE(audioSegment, path);
|
||||
recordWAVE(audioSegment, path, identifierCollection);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized recording format [" + recordFormat.name() + "]");
|
||||
@@ -76,16 +91,19 @@ public class AudioSegmentRecorder
|
||||
* Records the audio segment as an MP3 file to the specified path.
|
||||
* @param audioSegment to record
|
||||
* @param path for the recording
|
||||
* @param userPreferences for configuration
|
||||
* @param identifierCollection to use instead of the collection embedded in the audio segment
|
||||
* @throws IOException on any errors
|
||||
*/
|
||||
public static void recordMP3(AudioSegment audioSegment, Path path, UserPreferences userPreferences) throws IOException
|
||||
public static void recordMP3(AudioSegment audioSegment, Path path, UserPreferences userPreferences,
|
||||
IdentifierCollection identifierCollection) throws IOException
|
||||
{
|
||||
if(audioSegment.hasAudio())
|
||||
{
|
||||
OutputStream outputStream = new FileOutputStream(path.toFile());
|
||||
|
||||
//Write ID3 metadata
|
||||
Map<AudioMetadata,String> metadataMap = AudioMetadataUtils.getMetadataMap(audioSegment.getIdentifierCollection(),
|
||||
Map<AudioMetadata,String> metadataMap = AudioMetadataUtils.getMetadataMap(identifierCollection,
|
||||
audioSegment.getAliasList());
|
||||
|
||||
byte[] id3Bytes = AudioMetadataUtils.getMP3ID3(metadataMap);
|
||||
@@ -123,9 +141,10 @@ public class AudioSegmentRecorder
|
||||
* Records the audio segment as a WAVe file to the specified path.
|
||||
* @param audioSegment to record
|
||||
* @param path for the recording
|
||||
* @param identifierCollection to use instead of the audioSegment's embedded identifier collection
|
||||
* @throws IOException on any errors
|
||||
*/
|
||||
public static void recordWAVE(AudioSegment audioSegment, Path path) throws IOException
|
||||
public static void recordWAVE(AudioSegment audioSegment, Path path, IdentifierCollection identifierCollection) throws IOException
|
||||
{
|
||||
if(audioSegment.hasAudio())
|
||||
{
|
||||
@@ -136,7 +155,7 @@ public class AudioSegmentRecorder
|
||||
writer.writeData(ConversionUtils.convertToSigned16BitSamples(audioBuffer));
|
||||
}
|
||||
|
||||
Map<AudioMetadata,String> metadataMap = AudioMetadataUtils.getMetadataMap(audioSegment.getIdentifierCollection(),
|
||||
Map<AudioMetadata,String> metadataMap = AudioMetadataUtils.getMetadataMap(identifierCollection,
|
||||
audioSegment.getAliasList());
|
||||
|
||||
ByteBuffer listChunk = AudioMetadataUtils.getLISTChunk(metadataMap);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -273,19 +273,7 @@ public abstract class TunerController implements Tunable, ISourceEventProcessor,
|
||||
*/
|
||||
public long getFrequency()
|
||||
{
|
||||
long frequency;
|
||||
|
||||
try
|
||||
{
|
||||
getFrequencyControllerLock().lock();
|
||||
frequency = mFrequencyController.getFrequency();
|
||||
}
|
||||
finally
|
||||
{
|
||||
getFrequencyControllerLock().unlock();
|
||||
}
|
||||
|
||||
return frequency;
|
||||
return mFrequencyController.getFrequency();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -72,6 +72,7 @@ import io.github.dsheirer.source.tuner.sdrplay.api.device.Device;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.DeviceInfo;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.Rsp1Device;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.Rsp1aDevice;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.Rsp1bDevice;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.Rsp2Device;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.RspDuoDevice;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.RspDxDevice;
|
||||
@@ -87,6 +88,12 @@ import io.github.dsheirer.source.tuner.sdrplay.rsp1a.IControlRsp1a;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.Rsp1aTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.Rsp1aTunerController;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.Rsp1aTunerEditor;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1b.ControlRsp1b;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1b.DiscoveredRsp1bTuner;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1b.IControlRsp1b;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1b.Rsp1bTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1b.Rsp1bTunerController;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1b.Rsp1bTunerEditor;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp2.ControlRsp2;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp2.DiscoveredRsp2Tuner;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp2.IControlRsp2;
|
||||
@@ -148,6 +155,9 @@ public class TunerFactory
|
||||
case RSP1A:
|
||||
tuners.add(new DiscoveredRsp1aTuner(deviceInfo, channelizerType));
|
||||
break;
|
||||
case RSP1B:
|
||||
tuners.add(new DiscoveredRsp1bTuner(deviceInfo, channelizerType));
|
||||
break;
|
||||
case RSP2:
|
||||
tuners.add(new DiscoveredRsp2Tuner(deviceInfo, channelizerType));
|
||||
break;
|
||||
@@ -228,6 +238,14 @@ public class TunerFactory
|
||||
return new RspTuner(rsp1aTunerController, tunerErrorListener, channelizerType);
|
||||
}
|
||||
break;
|
||||
case RSP1B:
|
||||
if(device instanceof Rsp1bDevice rsp1bDevice)
|
||||
{
|
||||
IControlRsp1b controlRsp1b = new ControlRsp1b(rsp1bDevice);
|
||||
Rsp1bTunerController rsp1bTunerController = new Rsp1bTunerController(controlRsp1b, tunerErrorListener);
|
||||
return new RspTuner(rsp1bTunerController, tunerErrorListener, channelizerType);
|
||||
}
|
||||
break;
|
||||
case RSP2:
|
||||
if(device instanceof Rsp2Device rsp2Device)
|
||||
{
|
||||
@@ -411,6 +429,8 @@ public class TunerFactory
|
||||
return new Rsp1TunerConfiguration(uniqueID);
|
||||
case RSP_1A:
|
||||
return new Rsp1aTunerConfiguration(uniqueID);
|
||||
case RSP_1B:
|
||||
return new Rsp1bTunerConfiguration(uniqueID);
|
||||
case RSP_2:
|
||||
return new Rsp2TunerConfiguration(uniqueID);
|
||||
case RSP_DUO_1:
|
||||
@@ -451,6 +471,8 @@ public class TunerFactory
|
||||
return new Rsp1TunerEditor(userPreferences, tunerManager, discoveredRspTuner);
|
||||
case RSP1A:
|
||||
return new Rsp1aTunerEditor(userPreferences, tunerManager, discoveredRspTuner);
|
||||
case RSP1B:
|
||||
return new Rsp1bTunerEditor(userPreferences, tunerManager, discoveredRspTuner);
|
||||
case RSP2:
|
||||
return new Rsp2TunerEditor(userPreferences, tunerManager, discoveredRspTuner);
|
||||
case RSPdx:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -40,6 +40,7 @@ public enum TunerType
|
||||
RAFAELMICRO_R828D("R828D"),
|
||||
RSP_1("RSP1"),
|
||||
RSP_1A("RSP1A"),
|
||||
RSP_1B("RSP1B"),
|
||||
RSP_2("RSP2"),
|
||||
RSP_DUO_1("RSPduo Tuner 1"),
|
||||
RSP_DUO_2("RSPduo Tuner 2"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -59,13 +59,27 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
|
||||
* Logs current state of the tuner
|
||||
*/
|
||||
public void logState()
|
||||
{
|
||||
mLog.info(getDiagnosticReport());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a state report for this tuner.
|
||||
* @return
|
||||
*/
|
||||
public String getDiagnosticReport()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Discovered Tuner: ").append(getId());
|
||||
sb.append("\n\tClass:").append(getClass());
|
||||
|
||||
if(hasTuner())
|
||||
{
|
||||
sb.append("\n\tTuner - Frequency:").append(getTuner().getTunerController().getFrequency());
|
||||
sb.append("\n\tTuner Class:").append(getTuner().getClass());
|
||||
sb.append("\n\tTuner Controller Class:").append(getTuner().getTunerController().getClass());
|
||||
sb.append("\n\tFrequency:").append(getTuner().getTunerController().getFrequency());
|
||||
sb.append("\n\tError:").append(getErrorMessage());
|
||||
sb.append("\n\tChannel Manager Class:").append(getTuner().getChannelSourceManager().getClass());
|
||||
sb.append("\n\tChannel Manager:").append(getTuner().getChannelSourceManager().getStateDescription());
|
||||
}
|
||||
else
|
||||
@@ -73,7 +87,7 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
|
||||
sb.append("\n\tTuner - no tuner");
|
||||
}
|
||||
|
||||
mLog.info(sb.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -72,6 +72,7 @@ public class HeterodyneChannelSourceManager extends ChannelSourceManager
|
||||
.append("] Frequency [").append(channelSource.getFrequency())
|
||||
.append("] Mixer [").append(channelSource.getMixerFrequency())
|
||||
.append("]");
|
||||
sb.append(" HASH:").append(Integer.toHexString(channelSource.hashCode()).toUpperCase());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -776,16 +776,4 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.info("Starting ...");
|
||||
UserPreferences userPreferences = new UserPreferences();
|
||||
TunerManager tunerManager = new TunerManager(userPreferences);
|
||||
tunerManager.start();
|
||||
|
||||
while(true);
|
||||
|
||||
// mLog.info("Finished!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,7 +28,6 @@ import io.github.dsheirer.source.tuner.sdrplay.api.callback.IStreamListener;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.Device;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.TunerSelect;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.control.AgcMode;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.tuner.GainReduction;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.tuner.IfMode;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.tuner.LoMode;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
@@ -45,10 +44,11 @@ public abstract class ControlRsp<T extends Device> implements IControlRsp
|
||||
private static final Logger mLog = LoggerFactory.getLogger(ControlRsp.class);
|
||||
private T mDevice;
|
||||
protected RspSampleRate mSampleRate = RspSampleRate.RATE_8_000;
|
||||
protected int mGain;
|
||||
protected int mLNA;
|
||||
protected int mBasebandGainReduction;
|
||||
private IDeviceEventListener mDeviceEventListener;
|
||||
private IStreamListener mStreamListener;
|
||||
protected WeakReference<IGainOverloadListener> mGainOverloadReference;
|
||||
protected WeakReference<ITunerStatusListener> mGainOverloadReference;
|
||||
|
||||
//Streaming control lock and boolean status indicator. Access to the boolean indicator is protected by the lock.
|
||||
protected ReentrantLock mStreamingLock = new ReentrantLock();
|
||||
@@ -242,21 +242,28 @@ public abstract class ControlRsp<T extends Device> implements IControlRsp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gain index
|
||||
* @param gain index value (0 - 28)
|
||||
* @throws SDRPlayException
|
||||
*/
|
||||
@Override
|
||||
public void setGain(int gain) throws SDRPlayException
|
||||
public void setGain(int lna, int gr) throws SDRPlayException
|
||||
{
|
||||
validateGain(gain);
|
||||
int lnaLimited = Math.max(lna, 0);
|
||||
lnaLimited = Math.min(lnaLimited, getMaximumLNASetting());
|
||||
|
||||
if(gain != mGain)
|
||||
int grLimited = Math.max(gr, 20);
|
||||
grLimited = Math.min(grLimited, 59);
|
||||
|
||||
if(lna != lnaLimited || gr != grLimited)
|
||||
{
|
||||
mGain = gain;
|
||||
getDevice().getTuner().setGain(mGain);
|
||||
mLog.warn("Setting gain - adjusted LNA from [" + lna + "] to [" + lnaLimited + "] and GR from [" + gr + "] to [" + grLimited + "]");
|
||||
}
|
||||
getDevice().getTuner().setGain(lnaLimited, grLimited);
|
||||
mLNA = lnaLimited;
|
||||
mBasebandGainReduction = grLimited;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getCurrentGain() throws SDRPlayException
|
||||
{
|
||||
return getDevice().getTuner().getGain().getGainValues().getCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +271,7 @@ public abstract class ControlRsp<T extends Device> implements IControlRsp
|
||||
* @param listener to register
|
||||
*/
|
||||
@Override
|
||||
public void setGainOverloadListener(IGainOverloadListener listener)
|
||||
public void setGainOverloadListener(ITunerStatusListener listener)
|
||||
{
|
||||
if(mGainOverloadReference != null && mGainOverloadReference.get() != null)
|
||||
{
|
||||
@@ -289,7 +296,7 @@ public abstract class ControlRsp<T extends Device> implements IControlRsp
|
||||
//Notify an optional weakly referenced, registered listener that gain overload has been acknowledged.
|
||||
if(mGainOverloadReference != null)
|
||||
{
|
||||
IGainOverloadListener listener = mGainOverloadReference.get();
|
||||
ITunerStatusListener listener = mGainOverloadReference.get();
|
||||
|
||||
if(listener != null)
|
||||
{
|
||||
@@ -317,6 +324,17 @@ public abstract class ControlRsp<T extends Device> implements IControlRsp
|
||||
if(hasDevice())
|
||||
{
|
||||
getDevice().getTuner().setFrequency(frequency);
|
||||
|
||||
//Notify an optional weakly referenced, registered listener that gain overload has been acknowledged.
|
||||
if(mGainOverloadReference != null)
|
||||
{
|
||||
ITunerStatusListener listener = mGainOverloadReference.get();
|
||||
|
||||
if(listener != null)
|
||||
{
|
||||
ThreadPool.CACHED.submit(() -> listener.frequencyUpdated());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -343,27 +361,23 @@ public abstract class ControlRsp<T extends Device> implements IControlRsp
|
||||
}
|
||||
|
||||
/**
|
||||
* Current gain index
|
||||
* @return gain index
|
||||
* Current LNA gain setting
|
||||
* @return LNA gain
|
||||
*/
|
||||
@Override
|
||||
public int getGain()
|
||||
public int getLNA()
|
||||
{
|
||||
return mGain;
|
||||
return mLNA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the requested gain index setting is within the range of valid gain values.
|
||||
* @param gain to validate
|
||||
* @throws SDRPlayException if the gain index value is outside the range of valid values.
|
||||
* Current Baseband gain reduction setting
|
||||
* @return baseband gain reduction
|
||||
*/
|
||||
protected void validateGain(int gain) throws SDRPlayException
|
||||
@Override
|
||||
public int getBasebandGainReduction()
|
||||
{
|
||||
if(gain < GainReduction.MIN_GAIN_INDEX || gain > GainReduction.MAX_GAIN_INDEX)
|
||||
{
|
||||
throw new SDRPlayException("Invalid gain index value [" + gain + "]. Valid range is " +
|
||||
GainReduction.MIN_GAIN_INDEX + " - " + GainReduction.MAX_GAIN_INDEX);
|
||||
}
|
||||
return mBasebandGainReduction;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -116,16 +116,36 @@ public interface IControlRsp
|
||||
double getCurrentSampleRate();
|
||||
|
||||
/**
|
||||
* Sets the gain index value.
|
||||
* @param gain index value (0 - 28)
|
||||
* Set the lna state and gain reduction dB values separately.
|
||||
* @param lna state (varies by tuner and frequency range)
|
||||
* @param basebandGainReduction setting (20-59)
|
||||
* @throws SDRPlayException
|
||||
*/
|
||||
void setGain(int gain) throws SDRPlayException;
|
||||
void setGain(int lna, int basebandGainReduction) throws SDRPlayException;
|
||||
|
||||
/**
|
||||
* Current gain index value
|
||||
* @return gain index value (0 - 28)
|
||||
* Current gain setting as reported by the device.
|
||||
* @return gain value in decibels
|
||||
*/
|
||||
int getGain();
|
||||
float getCurrentGain() throws SDRPlayException;
|
||||
|
||||
/**
|
||||
* Current LNA gain index value
|
||||
* @return LNA gain value.
|
||||
*/
|
||||
int getLNA();
|
||||
|
||||
/**
|
||||
* Maximum LNA setting index value.
|
||||
* @return max LNA that varies by tuner type.
|
||||
*/
|
||||
int getMaximumLNASetting();
|
||||
|
||||
/**
|
||||
* Current gain reduction value.
|
||||
* @return gain reduction value.
|
||||
*/
|
||||
int getBasebandGainReduction();
|
||||
|
||||
/**
|
||||
* Current IF AGC mode setting.
|
||||
@@ -143,5 +163,5 @@ public interface IControlRsp
|
||||
* Registers a listener to receive notifications of gain overload.
|
||||
* @param listener to register
|
||||
*/
|
||||
void setGainOverloadListener(IGainOverloadListener listener);
|
||||
void setGainOverloadListener(ITunerStatusListener listener);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -24,7 +24,16 @@ import io.github.dsheirer.source.tuner.sdrplay.api.device.TunerSelect;
|
||||
/**
|
||||
* Listener interface to receive notifications of gain overload in the RSP device.
|
||||
*/
|
||||
public interface IGainOverloadListener
|
||||
public interface ITunerStatusListener
|
||||
{
|
||||
/**
|
||||
* Notify that there is a gain overload event in the specified tuner
|
||||
* @param tunerSelect to id the tuner
|
||||
*/
|
||||
void notifyGainOverload(TunerSelect tunerSelect);
|
||||
|
||||
/**
|
||||
* Notify that the tuner's frequency has changed.
|
||||
*/
|
||||
void frequencyUpdated();
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.control.AgcMode;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.tuner.GainReduction;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1.Rsp1TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1a.Rsp1aTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp1b.Rsp1bTunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rsp2.Rsp2TunerConfiguration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.RspDuoTuner1Configuration;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.RspDuoTuner2Configuration;
|
||||
@@ -39,6 +39,7 @@ import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerConfiguration;
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = Rsp1TunerConfiguration.class, name = "rsp1TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = Rsp1aTunerConfiguration.class, name = "rsp1aTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = Rsp1bTunerConfiguration.class, name = "rsp1bTunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = Rsp2TunerConfiguration.class, name = "rsp2TunerConfiguration"),
|
||||
@JsonSubTypes.Type(value = RspDuoTuner1Configuration.class, name = "rspDuoTuner1Configuration"),
|
||||
@JsonSubTypes.Type(value = RspDuoTuner2Configuration.class, name = "rspDuoTuner2Configuration"),
|
||||
@@ -50,7 +51,8 @@ public abstract class RspTunerConfiguration extends TunerConfiguration
|
||||
public static final RspSampleRate DEFAULT_DUAL_TUNER_SAMPLE_RATE = RspSampleRate.DUO_RATE_2_000;
|
||||
|
||||
private RspSampleRate mRspSampleRate = DEFAULT_SINGLE_TUNER_SAMPLE_RATE;
|
||||
private int mGain = 24;
|
||||
private int mLNA = 0;
|
||||
private int mBasebandGainReduction = 50;
|
||||
private AgcMode mAgcMode = AgcMode.ENABLE;
|
||||
|
||||
/**
|
||||
@@ -87,25 +89,41 @@ public abstract class RspTunerConfiguration extends TunerConfiguration
|
||||
}
|
||||
|
||||
/**
|
||||
* Gain index to use
|
||||
* @return gain index, 0 - 28
|
||||
* LNA setting
|
||||
* @return LNA setting
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "gain")
|
||||
public int getGain()
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "lna")
|
||||
public int getLNA()
|
||||
{
|
||||
return mGain;
|
||||
return mLNA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gain index
|
||||
* @param gain index, 0 - 28
|
||||
* Sets the LNA state
|
||||
* @param LNA state, 0 - xx, varies by tuner and frequency range.
|
||||
*/
|
||||
public void setGain(int gain)
|
||||
public void setLNA(int lna)
|
||||
{
|
||||
if(GainReduction.MIN_GAIN_INDEX <= gain && gain <= GainReduction.MAX_GAIN_INDEX)
|
||||
{
|
||||
mGain = gain;
|
||||
}
|
||||
mLNA = lna;
|
||||
}
|
||||
|
||||
/**
|
||||
* Baseband gain reduction
|
||||
* @return gain reduction in range 20-59 dB
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "gr")
|
||||
public int getBasebandGainReduction()
|
||||
{
|
||||
return mBasebandGainReduction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the baseband gain reduction value.
|
||||
* @param basebandGainReduction value
|
||||
*/
|
||||
public void setBasebandGainReduction(int basebandGainReduction)
|
||||
{
|
||||
mBasebandGainReduction = basebandGainReduction;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -91,11 +91,13 @@ public abstract class RspTunerController<I extends IControlRsp> extends TunerCon
|
||||
|
||||
try
|
||||
{
|
||||
getControlRsp().setGain(rtc.getGain());
|
||||
int lna = rtc.getLNA();
|
||||
int gr = rtc.getBasebandGainReduction();
|
||||
getControlRsp().setGain(lna, gr);
|
||||
}
|
||||
catch(SDRPlayException se)
|
||||
{
|
||||
mLog.error("Error setting RSP gain index to " + rtc.getGain());
|
||||
mLog.error("Error setting RSP LNA [" + rtc.getLNA() + "] and baseband [" + rtc.getBasebandGainReduction() + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
*
|
||||
* This 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,32 +24,37 @@ import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.SDRPlayException;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.device.TunerSelect;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.control.AgcMode;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.parameter.tuner.GainReduction;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.awt.Color;
|
||||
import java.awt.EventQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.JToggleButton;
|
||||
|
||||
/**
|
||||
* Abstract RSP tuner editor
|
||||
*/
|
||||
public abstract class RspTunerEditor<C extends RspTunerConfiguration> extends TunerEditor<RspTuner,C> implements IGainOverloadListener
|
||||
public abstract class RspTunerEditor<C extends RspTunerConfiguration> extends TunerEditor<RspTuner,C> implements ITunerStatusListener
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(RspTunerEditor.class);
|
||||
private JSlider mGainSlider;
|
||||
private JLabel mGainValueLabel;
|
||||
protected static final String MANUAL = "Manual";
|
||||
protected static final String AUTOMATIC = "Automatic";
|
||||
private JToggleButton mAgcButton;
|
||||
private JLabel mGainValueLabel;
|
||||
private LnaSlider mLNASlider;
|
||||
private IfGainSlider mIfGainSlider;
|
||||
private JButton mGainOverloadButton;
|
||||
private JPanel mGainPanel;
|
||||
private AtomicBoolean mGainOverloadAlert = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
@@ -69,40 +74,23 @@ public abstract class RspTunerEditor<C extends RspTunerConfiguration> extends Tu
|
||||
}
|
||||
|
||||
/**
|
||||
* Gain reduction slider control
|
||||
* Gain controls panel
|
||||
* @return gain panel
|
||||
*/
|
||||
protected JSlider getGainSlider()
|
||||
protected JPanel getGainPanel()
|
||||
{
|
||||
if(mGainSlider == null)
|
||||
if(mGainPanel == null)
|
||||
{
|
||||
mGainSlider = new JSlider(JSlider.HORIZONTAL, GainReduction.MIN_GAIN_INDEX,
|
||||
GainReduction.MAX_GAIN_INDEX, GainReduction.MIN_GAIN_INDEX);
|
||||
mGainSlider.setEnabled(false);
|
||||
mGainSlider.setMajorTickSpacing(1);
|
||||
mGainSlider.setPaintTicks(true);
|
||||
mGainSlider.addChangeListener(event ->
|
||||
{
|
||||
int gain = mGainSlider.getValue();
|
||||
|
||||
if(hasTuner() && !isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
getTunerController().getControlRsp().setGain(gain);
|
||||
save();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't set RSP gain to:" + gain, e);
|
||||
JOptionPane.showMessageDialog(mGainSlider, "Couldn't set RSP gain value to " + gain);
|
||||
}
|
||||
}
|
||||
|
||||
getGainValueLabel().setText(String.valueOf(gain));
|
||||
});
|
||||
mGainPanel = new JPanel();
|
||||
mGainPanel.setLayout(new MigLayout("insets 0","[][][grow,fill][][]",""));
|
||||
mGainPanel.add(getGainValueLabel());
|
||||
mGainPanel.add(getGainOverloadButton());
|
||||
mGainPanel.add(new JLabel()); //empty label to grow to fill space
|
||||
mGainPanel.add(new JLabel("IF Gain Mode:"));
|
||||
mGainPanel.add(getAgcButton());
|
||||
}
|
||||
|
||||
return mGainSlider;
|
||||
return mGainPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,7 +114,7 @@ public abstract class RspTunerEditor<C extends RspTunerConfiguration> extends Tu
|
||||
{
|
||||
if(mAgcButton == null)
|
||||
{
|
||||
mAgcButton = new JToggleButton("IF AGC");
|
||||
mAgcButton = new JToggleButton(MANUAL);
|
||||
mAgcButton.setEnabled(false);
|
||||
mAgcButton.addActionListener(e -> {
|
||||
if(hasTuner() && !isLoading())
|
||||
@@ -134,6 +122,7 @@ public abstract class RspTunerEditor<C extends RspTunerConfiguration> extends Tu
|
||||
try
|
||||
{
|
||||
getTunerController().getControlRsp().setAgcMode(mAgcButton.isSelected() ? AgcMode.ENABLE : AgcMode.DISABLE);
|
||||
updateGainLabel();
|
||||
}
|
||||
catch(SDRPlayException se)
|
||||
{
|
||||
@@ -141,6 +130,17 @@ public abstract class RspTunerEditor<C extends RspTunerConfiguration> extends Tu
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
getIfGainSlider().setEnabled(!getAgcButton().isSelected());
|
||||
|
||||
if(mAgcButton.isSelected())
|
||||
{
|
||||
getAgcButton().setText(AUTOMATIC);
|
||||
}
|
||||
else
|
||||
{
|
||||
getAgcButton().setText(MANUAL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,6 +175,12 @@ public abstract class RspTunerEditor<C extends RspTunerConfiguration> extends Tu
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void frequencyUpdated()
|
||||
{
|
||||
updateLnaSlider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the alert styling of the disabled gain overload button to indicate an alert condition or a normal
|
||||
* operating condition.
|
||||
@@ -194,9 +200,177 @@ public abstract class RspTunerEditor<C extends RspTunerConfiguration> extends Tu
|
||||
getGainOverloadButton().setForeground(getForeground());
|
||||
getGainOverloadButton().setBackground(getBackground());
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the LNA slider with the number of LNA states available for the current frequency.
|
||||
*/
|
||||
protected void updateLnaSlider()
|
||||
{
|
||||
if(hasTuner())
|
||||
{
|
||||
mLog.info("Ignoring duplicate gain alerting - alert[" + alert + "] atomic [" + mGainOverloadAlert.get() + "]");
|
||||
int max = getTunerController().getControlRsp().getMaximumLNASetting();
|
||||
if(max != getLNASlider().getMaximum())
|
||||
{
|
||||
EventQueue.invokeLater(() -> {
|
||||
setLoading(true);
|
||||
//Adjust the value if it's less than max
|
||||
getLNASlider().setValue(Math.min(getLNASlider().getValue(), max));
|
||||
getLNASlider().setMaximum(max);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LNA Gain Slider
|
||||
*/
|
||||
protected LnaSlider getLNASlider()
|
||||
{
|
||||
if(mLNASlider == null)
|
||||
{
|
||||
mLNASlider = new LnaSlider();
|
||||
mLNASlider.setEnabled(true);
|
||||
mLNASlider.setMajorTickSpacing(1);
|
||||
mLNASlider.setPaintTicks(true);
|
||||
mLNASlider.addChangeListener(event ->
|
||||
{
|
||||
JSlider source = (JSlider)event.getSource();
|
||||
|
||||
if(!source.getValueIsAdjusting())
|
||||
{
|
||||
updateGain();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mLNASlider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the gain settings when the LNA or Baseband gain value is changed by the user.
|
||||
*/
|
||||
private void updateGain()
|
||||
{
|
||||
int lna = getLNASlider().getLNA();
|
||||
int gr = getIfGainSlider().getGR();
|
||||
|
||||
if(hasTuner() && !isLoading())
|
||||
{
|
||||
try
|
||||
{
|
||||
getTunerController().getControlRsp().setGain(lna, gr);
|
||||
save();
|
||||
updateGainLabel();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't set RSP gain to LNA:" + lna + " Gain Reduction:" + gr, e);
|
||||
JOptionPane.showMessageDialog(mIfGainSlider, "Couldn't set RSP gain value to LNA:" + lna + " Gain Reduction:" + gr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateGainLabel()
|
||||
{
|
||||
try
|
||||
{
|
||||
float currentGain = getTunerController().getControlRsp().getCurrentGain();
|
||||
getGainValueLabel().setText((int)currentGain + " dB");
|
||||
}
|
||||
catch(SDRPlayException se)
|
||||
{
|
||||
mLog.error("Error accessing current gain value from RSP tuner", se);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IF Gain Slider
|
||||
*/
|
||||
protected IfGainSlider getIfGainSlider()
|
||||
{
|
||||
if(mIfGainSlider == null)
|
||||
{
|
||||
mIfGainSlider = new IfGainSlider();
|
||||
mIfGainSlider.setEnabled(true);
|
||||
mIfGainSlider.setMajorTickSpacing(1);
|
||||
mIfGainSlider.setPaintTicks(true);
|
||||
mIfGainSlider.addChangeListener(event ->
|
||||
{
|
||||
JSlider source = (JSlider)event.getSource();
|
||||
|
||||
if(!source.getValueIsAdjusting())
|
||||
{
|
||||
updateGain();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mIfGainSlider;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSlider implementation that inverts the scale to support LNA values.
|
||||
*/
|
||||
public class LnaSlider extends JSlider
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public LnaSlider()
|
||||
{
|
||||
super(JSlider.HORIZONTAL, 0,9,9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slider value converted to the LNA scale.
|
||||
* @return lna value.
|
||||
*/
|
||||
public int getLNA()
|
||||
{
|
||||
return getMaximum() - getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the slider with the value converted from the provided LNA value.
|
||||
* @param lna value to apply
|
||||
*/
|
||||
public void setLNA(int lna)
|
||||
{
|
||||
setValue(getMaximum() - lna);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSlider implementation that inverts the scale to support IF Gain (aka Gain Reduction) values.
|
||||
*/
|
||||
public class IfGainSlider extends JSlider
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public IfGainSlider()
|
||||
{
|
||||
super(JSlider.HORIZONTAL, 0, 39, 30);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slider value converted to the gain reduction scale.
|
||||
* @return gain reduction value.
|
||||
*/
|
||||
public int getGR()
|
||||
{
|
||||
return getIfGainSlider().getMaximum() - getIfGainSlider().getValue() + 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the slider with the value converted from the provided gain reduction value.
|
||||
* @param gainReduction value to apply
|
||||
*/
|
||||
public void setGR(int gainReduction)
|
||||
{
|
||||
setValue(getMaximum() - (gainReduction - 20));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user