Compare commits

...

40 Commits

Author SHA1 Message Date
Dennis Sheirer
c96349fa1c Updates version to 0.6.0 Beta 2 2023-08-31 07:14:58 -04:00
Denny Sheirer
f640576cc3 Merge pull request #1646 from DSheirer/1637-dmr-cap-plus-and-max-trunking
#1637 DMR Cap+/CapMax Channel Frequency Map / Trunking Issue
2023-08-31 07:10:21 -04:00
Dennis Sheirer
77d725a488 #1637 Resolves issue with DMR Cap+/CapMax trunking channel to frequency map. Updates the DMR channel map to use LCN instead of LSN and aligns standard DMR with Tier-III DMR for channel identification.
Warning: breaking change.  Users will need to either recreate their DMR channel to frequency maps or simply delete and reload the channel from Radio Reference to correct the channel to frequency map.

Updates the DMR Decoder State to better track and update DMR events versus creating a new event and causing lots of scrolling in the event window of the Now Playing tab.
2023-08-31 07:06:50 -04:00
Denny Sheirer
2c0305f928 Merge pull request #1642 from DSheirer/1641-hytera-rrs-packets
#1641 DMR Hytera RRS packet support.
2023-08-27 07:47:14 -04:00
Dennis Sheirer
21fd61d507 #1641 DMR Hytera RRS packet support. 2023-08-27 07:44:43 -04:00
Denny Sheirer
8fb56a5342 Merge pull request #1640 from DSheirer/1638-hytera-encryption-parameters
#1638 DMR Decoder - Encryption Parameters & Hytera RRS
2023-08-26 07:36:31 -04:00
Dennis Sheirer
5d7b73f876 #1638 Enhances DMR decoder to include additional encryption details from two major vendor implementations. Adds Hytera short data Radio Registration Service (RRS) processing. 2023-08-26 07:32:46 -04:00
Dennis Sheirer
85b2a38058 Version 0.6.0 Beta 1 2023-08-19 05:16:20 -04:00
Denny Sheirer
c78517ee75 Merge pull request #1631 from DSheirer/1630-flc-arc4-encryption-parameters
#1630 DMR FLC ARC4/EP Encryption Parameters
2023-08-18 07:08:30 -04:00
Dennis Sheirer
ae116d3dfe #1630 Updates ARC4 FLC encryption parameters message to detect and parse both Hytera & Motorola message formats. 2023-08-18 07:06:26 -04:00
Denny Sheirer
93252f9a40 Merge pull request #1629 from DSheirer/1628-libusb-device-list-error
#1628 LibUsb Error -99 During Device Discovery
2023-08-18 03:57:50 -04:00
Dennis Sheirer
3c03a4db2e #1628 Handle libusb get device list error condition and retry with increased logging during error code -99 to determine what causes this error condition, primarily on some Linux systems. 2023-08-18 03:55:42 -04:00
Denny Sheirer
d0fcd617aa Merge pull request #1627 from DSheirer/1626-dmr-call-events-missing-timeslot
#1626 Updates DMR call events to include timeslot and adds DMR encryption info when available.
2023-08-15 04:01:52 -04:00
Dennis Sheirer
6ca0decbd6 #1626 Updates DMR call events to include timeslot and adds DMR encryption information when available. 2023-08-15 04:01:27 -04:00
Denny Sheirer
ddd9ca933e Merge pull request #1624 from DSheirer/1623-unknown-opcode-message-filter
#1623 P25 Message Filter Set - Unknown Vendor Opcode
2023-08-14 04:04:30 -04:00
Dennis Sheirer
b2cc29569e #1623 resolves issue with handling of unknown P25 trunking opcode in message filter set. 2023-08-14 04:03:06 -04:00
Denny Sheirer
411735772b Merge pull request #1622 from DSheirer/dmr-cap-plus-identifier-updates
#1618 DMR Decoder Message Enhancements
2023-08-13 08:38:12 -04:00
sheirerd
2ffb2963af #1618 Update Cap+ radio & group parsing, process TXI & voice frame encryption parameters, and many other changes to the DMR message processing. 2023-08-13 08:30:23 -04:00
Denny Sheirer
227c88c8a2 Merge pull request #1614 from DSheirer/1612-dcs-alias-identifier
#1612 Enhance aliasing to support DCS tones
2023-07-24 04:34:46 -04:00
Dennis Sheirer
cbe9c79498 #1612 Enhance aliasing to support DCS tones 2023-07-24 04:30:16 -04:00
Denny Sheirer
1b8c100b43 Merge pull request #1611 from DSheirer/1605-tuner-editor-fails-to-display
#1605 Blank Tuner Editor Panel on Certain Linux Configurations
2023-07-22 06:00:38 -04:00
Dennis Sheirer
30c5fd64f2 #1605 Resolves issue where certain Linux users were unable to display the tuner editor panel due to an issue with the frequency control editor's usage of custom cursor. 2023-07-22 05:55:43 -04:00
Denny Sheirer
3a6b486340 Merge pull request #1610 from DSheirer/1604-event-panel-row-layout
#1604 Event Panel UI Layout Creates Excess Space On Resizing
2023-07-22 05:25:11 -04:00
Dennis Sheirer
ded7335dbf #1604 Resolves issue with event panel UI layout creating excess space between history management panel and events table. 2023-07-22 05:21:26 -04:00
Denny Sheirer
005a4f8119 Merge pull request #1608 from DSheirer/1603-duplicate-events
#1603 De-Duplicate Call Events
2023-07-22 05:06:11 -04:00
Dennis Sheirer
1ac49bfa7f #1603 Resolves issue with de-duplicating events to allow call duration values to display correctly and minimize the quantity of duplicate events. 2023-07-22 04:57:26 -04:00
Denny Sheirer
c302c4d121 Merge pull request #1601 from DSheirer/1368-decode-event-filtering-2
1368 Enhance Message & Event Filtering Options
2023-07-14 06:51:57 -04:00
Dennis Sheirer
b0a524cf62 #1368 Restructures Message and Event filter framework. 2023-07-14 06:50:07 -04:00
Christopher Varner
d246ffff30 Adds event type as a filtering option in the Events tab 2023-07-03 05:56:50 -04:00
Denny Sheirer
adfc8b2d6f #1586 Resolves issue in P25 message framer where after transmitting the current message the mDataUnitId is set to null and this causes an NPE when subsequent code is executed prior to shutdown of the channel. (#1593)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-03 05:11:20 -04:00
Denny Sheirer
ad2ab08466 #1588 Use JDK20 and Gradle 8.2. Updates SDRPlay API for compatibility with JDK20 Project Panama Foreign Memory/Function changes. (#1589)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-02 06:42:27 -04:00
Denny Sheirer
f3369410b2 #1590 Resolves issue with RSP tuners generating too many native buffers causing excessive memory consumption (#1591)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-07-02 06:23:00 -04:00
Denny Sheirer
bac38a5bd8 #1584 Updates DMR & P25 decoders to stop processing residual samples from the current buffer after a traffic channel shutdown has been signalled. (#1585)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-25 03:56:08 -04:00
Denny Sheirer
e5f5df74ad #1563 Updates RSP tuner to produce sample buffers with optimal power-of-2 length to enable downstream vectorized operations. (#1583)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 09:09:30 -04:00
Denny Sheirer
b3837f581a #1581 Updates Rdio Scanner configuration editor to automatically append the 'api/call-upload' path to the end of the protocol and host value provided by the user. Sets the host default value to 'http://localhost' but allows the user to overwrite. (#1582)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2023-06-24 07:05:44 -04:00
Denny Sheirer
b2ab6f58e9 #1579 Resolves issue with array index out of bounds error generated in P25 Phase 1 decoders where the Dibit circular buffer attempts to write a (#1580)
dibit past the length of the array.  Issue likely occurs when a partial/incomplete put operation adds a dibit and increments the buffer pointer but the thread is interrupted prior to wrapping the buffer pointer.  When the next runnable iteration executes, the pointer is pointing past the end of the buffer.  Updates the code to check the pointer both before and after the put operation.

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

View File

@@ -22,13 +22,13 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 19
uses: actions/setup-java@v2
- name: Set up JDK 20
uses: actions/setup-java@v3
with:
java-version: '19'
java-version: '20'
distribution: 'liberica'
java-package: 'jdk+fx'
- name: Build with Gradle
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
uses: gradle/gradle-build-action@v2
with:
arguments: clean build

2
.idea/misc.xml generated
View File

@@ -4,7 +4,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_19_PREVIEW" project-jdk-name="19" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,230 @@
#ifndef SDRPLAY_API_H
#define SDRPLAY_API_H
#include "sdrplay_api_dev.h"
#include "sdrplay_api_rx_channel.h"
#include "sdrplay_api_callback.h"
//#if defined(_M_X64) || defined(_M_IX86)
//#include "windows.h"
//#elif defined (__GNUC__)
typedef void *HANDLE;
//#endif
//
//#ifndef _SDRPLAY_DLL_QUALIFIER
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
//#elif defined(STATIC_LIB) || defined(__GNUC__)
//#define _SDRPLAY_DLL_QUALIFIER
//#endif
//#endif // _SDRPLAY_DLL_QUALIFIER
// Application code should check that it is compiled against the same API version
// sdrplay_api_ApiVersion() returns the API version
#define SDRPLAY_API_VERSION (float)(3.08)
// API Constants
#define SDRPLAY_MAX_DEVICES (16)
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
#define SDRPLAY_MAX_SER_NO_LEN (64)
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
#define SDRPLAY_RSP1_ID (1)
#define SDRPLAY_RSP1A_ID (255)
#define SDRPLAY_RSP2_ID (2)
#define SDRPLAY_RSPduo_ID (3)
#define SDRPLAY_RSPdx_ID (4)
// Enum types
typedef enum
{
sdrplay_api_Success = 0,
sdrplay_api_Fail = 1,
sdrplay_api_InvalidParam = 2,
sdrplay_api_OutOfRange = 3,
sdrplay_api_GainUpdateError = 4,
sdrplay_api_RfUpdateError = 5,
sdrplay_api_FsUpdateError = 6,
sdrplay_api_HwError = 7,
sdrplay_api_AliasingError = 8,
sdrplay_api_AlreadyInitialised = 9,
sdrplay_api_NotInitialised = 10,
sdrplay_api_NotEnabled = 11,
sdrplay_api_HwVerError = 12,
sdrplay_api_OutOfMemError = 13,
sdrplay_api_ServiceNotResponding = 14,
sdrplay_api_StartPending = 15,
sdrplay_api_StopPending = 16,
sdrplay_api_InvalidMode = 17,
sdrplay_api_FailedVerification1 = 18,
sdrplay_api_FailedVerification2 = 19,
sdrplay_api_FailedVerification3 = 20,
sdrplay_api_FailedVerification4 = 21,
sdrplay_api_FailedVerification5 = 22,
sdrplay_api_FailedVerification6 = 23,
sdrplay_api_InvalidServiceVersion = 24
} sdrplay_api_ErrT;
typedef enum
{
sdrplay_api_Update_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_Dev_Fs = 0x00000001,
sdrplay_api_Update_Dev_Ppm = 0x00000002,
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
// Reasons for master and slave mode
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
sdrplay_api_Update_Tuner_Gr = 0x00008000,
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
sdrplay_api_Update_Tuner_Frf = 0x00020000,
sdrplay_api_Update_Tuner_BwType = 0x00040000,
sdrplay_api_Update_Tuner_IfType = 0x00080000,
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
} sdrplay_api_ReasonForUpdateT;
typedef enum
{
sdrplay_api_Update_Ext1_None = 0x00000000,
// Reasons for master only mode
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
// Reasons for master and slave mode
} sdrplay_api_ReasonForUpdateExtension1T;
typedef enum
{
sdrplay_api_DbgLvl_Disable = 0,
sdrplay_api_DbgLvl_Verbose = 1,
sdrplay_api_DbgLvl_Warning = 2,
sdrplay_api_DbgLvl_Error = 3,
sdrplay_api_DbgLvl_Message = 4,
} sdrplay_api_DbgLvl_t;
// Device structure
typedef struct
{
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
unsigned char hwVer;
sdrplay_api_TunerSelectT tuner;
sdrplay_api_RspDuoModeT rspDuoMode;
unsigned char valid;
double rspDuoSampleFreq;
HANDLE dev;
} sdrplay_api_DeviceT;
// Device parameter structure
typedef struct
{
sdrplay_api_DevParamsT *devParams;
sdrplay_api_RxChannelParamsT *rxChannelA;
sdrplay_api_RxChannelParamsT *rxChannelB;
} sdrplay_api_DeviceParamsT;
// Extended error message structure
typedef struct
{
char file[256];
char function[256];
int line;
char message[1024];
} sdrplay_api_ErrorInfoT;
// Comman API function types
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
// Device API function types
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate, double newSampleRate);
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// API function definitions
#ifdef __cplusplus
extern "C"
{
#endif
// // Comman API function definitions
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Open(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Close(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
//
// // Device API function definitions
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
// sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
// sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
#ifdef __cplusplus
}
#endif
#endif //SDRPLAY_API_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -53,12 +53,12 @@ repositories {
maven { url "https://jitpack.io" }
}
version = '0.6.0-alpha6'
version = '0.6.0-beta2'
//Java 19 is required for this version of the Project Panama preview/incubator feature
//Java 20 is required for this version of the Project Panama preview/incubator feature
java {
toolchain {
languageVersion = JavaLanguageVersion.of(19)
languageVersion = JavaLanguageVersion.of(20)
}
}
@@ -197,16 +197,16 @@ String targetWindowsX86_64 = 'windows-x86_64-v' + version
* author's development environment, but can be changed to your environment if you want to use this build pattern.
*/
String jdk_base = '/home/denny/java_jdks/'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-20.0.1-full'
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-20.0.1-full'
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-20.0.1-full.jdk'
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-20.0.1-full.jdk'
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-20.0.1-full'
/**
* Download URLs to download the JDK as part of the gradle build packaging process
*/
def jdk_download_base = "https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-"
def jdk_download_base = "https://download.bell-sw.com/java/20.0.1+10/bellsoft-jdk20.0.1+10-"
def jdk_download_suffix = "-full.tar.gz"
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
def jdk_download_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix

View File

@@ -1,6 +1,6 @@
#
# ******************************************************************************
# Copyright (C) 2014-2022 Dennis Sheirer
# Copyright (C) 2014-2023 Dennis Sheirer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,7 +19,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,22 +19,15 @@
package io.github.dsheirer.alias;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.alias.action.beep.BeepAction;
import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.Radio;
@@ -45,11 +38,10 @@ import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AliasFactory
{
@@ -64,6 +56,11 @@ public class AliasFactory
BroadcastChannel copyBroadcast = new BroadcastChannel();
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
return copyBroadcast;
case DCS:
Dcs originalDcs = (Dcs)id;
Dcs copyDcs = new Dcs();
copyDcs.setDCSCode(originalDcs.getDCSCode());
return copyDcs;
case ESN:
Esn originalESN = (Esn)id;
Esn copyESN = new Esn();
@@ -234,65 +231,4 @@ public class AliasFactory
return copy;
}
public static AliasID getAliasID(AliasIDType type)
{
switch(type)
{
case BROADCAST_CHANNEL:
return new BroadcastChannel();
case ESN:
return new Esn();
case FLEETSYNC:
return new FleetsyncID();
case LTR_NET_UID:
return new UniqueID();
case LOJACK:
return new LoJackFunctionAndID();
case MDC1200:
return new MDC1200ID();
case MIN:
return new Min();
case MPT1327:
return new MPT1327ID();
case NON_RECORDABLE:
return new NonRecordable();
case PRIORITY:
return new Priority();
case RADIO_ID:
return new Radio();
case RADIO_ID_RANGE:
return new RadioRange();
case RECORD:
return new Record();
case TALKGROUP:
return new Talkgroup();
case TALKGROUP_RANGE:
return new TalkgroupRange();
case SITE:
return new SiteID();
case STATUS:
return new UserStatusID();
case LEGACY_TALKGROUP:
return new LegacyTalkgroupID();
default:
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
}
}
public static AliasAction getAliasAction(AliasActionType type)
{
switch(type)
{
case BEEP:
return new BeepAction();
case CLIP:
return new ClipAction();
case SCRIPT:
return new ScriptAction();
default:
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ package io.github.dsheirer.alias;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.priority.Priority;
import io.github.dsheirer.alias.id.radio.Radio;
@@ -32,6 +33,7 @@ import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.alias.id.tone.TonesID;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
import io.github.dsheirer.identifier.esn.ESNIdentifier;
import io.github.dsheirer.identifier.patch.PatchGroup;
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
@@ -41,12 +43,8 @@ import io.github.dsheirer.identifier.status.UserStatusIdentifier;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.module.decode.dcs.DCSCode;
import io.github.dsheirer.protocol.Protocol;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -57,6 +55,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
@@ -67,6 +69,7 @@ public class AliasList
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
private Map<DCSCode,Alias> mDCSCodeAliasMap = new EnumMap<>(DCSCode.class);
private Map<String,Alias> mESNMap = new HashMap<>();
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
@@ -178,6 +181,12 @@ public class AliasList
radioRangeAliasList.add(radioRange, alias);
break;
case DCS:
if(id instanceof Dcs dcs)
{
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
}
break;
case ESN:
String esn = ((Esn)id).getEsn();
@@ -418,7 +427,7 @@ public class AliasList
return toList(radioAliasList.getAlias(radio));
}
break;
case ESN:
case DCS:
if(identifier instanceof ESNIdentifier)
{
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
@@ -439,9 +448,9 @@ public class AliasList
}
break;
case TONE:
if(identifier instanceof ToneIdentifier)
if(identifier instanceof ToneIdentifier toneIdentifier)
{
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
ToneSequence toneSequence = toneIdentifier.getValue();
if(toneSequence != null && toneSequence.hasTones())
{
@@ -454,6 +463,15 @@ public class AliasList
}
}
}
else if(identifier instanceof DCSIdentifier dcsIdentifier)
{
DCSCode dcsCode = dcsIdentifier.getValue();
if(dcsCode != null)
{
return toList(mDCSCodeAliasMap.get(dcsCode));
}
}
break;
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id;
@@ -26,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
@@ -54,6 +52,7 @@ import javafx.util.Callback;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id;
@@ -26,6 +23,7 @@ import java.util.EnumSet;
public enum AliasIDType
{
BROADCAST_CHANNEL("Audio Broadcast Channel"),
DCS("Digital Coded Squelch (DCS)"),
ESN("ESN"),
INVERT("Audio Inversion"),
LOJACK("LoJack"),

View File

@@ -0,0 +1,111 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.alias.id.dcs;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.module.decode.dcs.DCSCode;
/**
* Digital Coded Squelch (DCS) tone identifier
*/
public class Dcs extends AliasID implements Comparable<Dcs>
{
private DCSCode mDCSCode;
@Override
public AliasIDType getType()
{
return AliasIDType.DCS;
}
@Override
public boolean matches(AliasID id)
{
if(isValid() && id instanceof Dcs other)
{
return other.isValid() && getDCSCode().equals(other.getDCSCode());
}
return false;
}
/**
* DCS code
* @return DCS code or null.
*/
@JacksonXmlProperty(isAttribute = true, localName = "code")
public DCSCode getDCSCode()
{
return mDCSCode;
}
/**
* Sets the DCS code value.
* @param dcsCode to set
*/
public void setDCSCode(DCSCode dcsCode)
{
mDCSCode = dcsCode;
updateValueProperty();
}
@Override
public boolean isValid()
{
return mDCSCode != null;
}
@Override
public boolean isAudioIdentifier()
{
return false;
}
@Override
public String toString()
{
if(isValid())
{
return getDCSCode().toString();
}
return "DCS-Invalid - No Tone Selected";
}
@Override
public int compareTo(Dcs o)
{
if(isValid())
{
if(o.isValid())
{
return getDCSCode().compareTo(o.getDCSCode());
}
else
{
return 1;
}
}
return -1;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -43,6 +43,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
{
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
private static float[] sHighPassFilterCoefficients;
private final boolean mAudioFilterEnable;
static
{
@@ -70,27 +71,33 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
}
}
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private SquelchState mSquelchState = SquelchState.SQUELCH;
/**
* Creates an Audio Module.
*
* @param aliasList for aliasing identifiers
* @param timeslot for this audio module
* @param maxAudioSegmentLength in milliseconds
* @param audioFilterEnable to enable or disable high-pass audio filter
*/
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, boolean audioFilterEnable)
{
super(aliasList, timeslot, maxAudioSegmentLength);
mAudioFilterEnable = audioFilterEnable;
}
/**
* Creates an Audio Module.
* @param aliasList for aliasing identifiers
* @param audioFilterEnable to enable or disable high-pass audio filter
*/
public AudioModule(AliasList aliasList)
public AudioModule(AliasList aliasList, boolean audioFilterEnable)
{
super(aliasList);
mAudioFilterEnable = audioFilterEnable;
}
@Override
@@ -121,7 +128,11 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
{
if(mSquelchState == SquelchState.UNSQUELCH)
{
audioBuffer = mHighPassFilter.filter(audioBuffer);
if(mAudioFilterEnable)
{
audioBuffer = mHighPassFilter.filter(audioBuffer);
}
addAudio(audioBuffer);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,9 +24,7 @@ import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
@@ -62,6 +60,11 @@ public class RdioScannerConfiguration extends BroadcastConfiguration
mValid.unbind();
mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
Bindings.isNotNull(mHost)));
if(mHost.getValue() == null || mHost.getValue().isEmpty())
{
mHost.set("http://localhost");
}
}
/**

View File

@@ -276,8 +276,7 @@ public class BinaryMessage extends BitSet
}
else
{
throw new BitSetFullException("bitset is full -- contains " +
(mPointer + 1) + "bits");
throw new BitSetFullException("bitset is full -- contains [" + mPointer + "/" + size() + "] bits");
}
}

View File

@@ -132,12 +132,10 @@ public enum SyncPattern
true, true, false, false //1100 0xC
}),
/* Sync (0x158) = 1101011000 */
/* Sync (0x158) = 101011000 */
PASSPORT( new boolean[]
{
true, //0001 0x1
false, true, false, true, //0101 0x5
true, false, false, false //1000 0x8
true,false,true,false,true,true,false,false,false
} ),
LTR_STANDARD_OSW( new boolean[]

View File

@@ -22,6 +22,8 @@ package io.github.dsheirer.buffer;
import io.github.dsheirer.sample.SampleUtils;
import io.github.dsheirer.sample.complex.ComplexSamples;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
import java.util.Arrays;
import java.util.Iterator;
/**
@@ -30,6 +32,7 @@ import java.util.Iterator;
public class FloatNativeBuffer extends AbstractNativeBuffer
{
private float[] mInterleavedComplexSamples;
private static final int BUFFER_SIZE = 2048;
/**
* Constructs an instance
@@ -81,48 +84,51 @@ public class FloatNativeBuffer extends AbstractNativeBuffer
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
{
private boolean mEmpty;
private int mBufferPointer = 0;
@Override
public boolean hasNext()
{
return !mEmpty;
return mBufferPointer < mInterleavedComplexSamples.length;
}
@Override
public ComplexSamples next()
{
if(mEmpty)
if(!hasNext())
{
throw new IllegalStateException("No more samples");
}
mEmpty = true;
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
mBufferPointer += BUFFER_SIZE * 2;
return SampleUtils.deinterleave(chunk, getTimestamp());
}
}
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
{
private boolean mEmpty;
private int mBufferPointer = 0;
@Override
public boolean hasNext()
{
return !mEmpty;
return mBufferPointer < mInterleavedComplexSamples.length;
}
@Override
public InterleavedComplexSamples next()
{
if(mEmpty)
if(!hasNext())
{
throw new IllegalStateException("No more samples");
}
mEmpty = true;
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
mBufferPointer += BUFFER_SIZE * 2;
return new InterleavedComplexSamples(chunk, getTimestamp());
}
}
}

View File

@@ -35,11 +35,11 @@ import javax.swing.JPanel;
public class NowPlayingPanel extends JPanel
{
private ChannelMetadataPanel mChannelMetadataPanel;
private ChannelDetailPanel mChannelDetailPanel;
private DecodeEventPanel mDecodeEventPanel;
private MessageActivityPanel mMessageActivityPanel;
private ChannelPowerPanel mChannelPowerPanel;
private final ChannelMetadataPanel mChannelMetadataPanel;
private final ChannelDetailPanel mChannelDetailPanel;
private final DecodeEventPanel mDecodeEventPanel;
private final MessageActivityPanel mMessageActivityPanel;
private final ChannelPowerPanel mChannelPowerPanel;
/**
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,

View File

@@ -23,6 +23,7 @@ import io.github.dsheirer.buffer.INativeBufferProvider;
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.log.LoggingSuppressor;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
@@ -65,6 +66,7 @@ import org.slf4j.LoggerFactory;
public class PolyphaseChannelManager implements ISourceEventProcessor
{
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LoggerFactory.getLogger(PolyphaseChannelManager.class));
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
private static final double CHANNEL_OVERSAMPLING = 2.0;
@@ -203,7 +205,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
}
catch(IllegalArgumentException iae)
{
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
LOGGING_SUPPRESSOR.error(iae.getMessage(), 3, "Couldn't allocate channel. " + iae.getMessage());
channelSource = null;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,61 +16,64 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.fsk;
import io.github.dsheirer.bits.MessageFramer;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.dc.IIRSinglePoleDCRemovalFilter;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
import io.github.dsheirer.sample.Listener;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Logic Trunked Radio (LTR) 300-baud FSK decoder is designed to operate against an 8 kHz FM demodulated audio stream.
* LTR decoder designed to work with 8 kHz input audio samples and decode 300-baud signalling.
*
* This decoder employs a zero-crossing timing error detector with adjustable symbol timing error gain. This decoder
* also employs a synchronization monitor that automatically adjusts symbol timing error gain according to the quantity
* of successful message sync pattern detections. This is optimized for LTR signals that periodically transmit idle
* bursts and transmit continuous message streams during voice transmissions. The synchronization monitor adjusts to an
* aggressive gain value when sync is lost and automatically reduces timing error gain as successive sync pattern
* detections occur.
* This decoder monitors the incoming sample stream looking for either a positive or negative sequence of samples where
* the energy delta across the baud period exceeds a threshold value and causes the symbol state to flip. The symbol
* state remains across successive baud periods until the next state change sequence is detected.
*
* FM demodulated sample buffers should not be filtered prior to this decoder. An internal DC-removal filter coupled
* with a low-pass filter removes any tuning offset and/or audio components above 300 Hertz. The DC removal filter is
* adjusted for a slower offset removal in order to prevent overly distorting the FSK signalling component. This mild
* adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However,
* the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission.
* Valid LTR messages start with a zero symbol and transition to a positive symbol for the sync bit, this decoder
* monitors for excessively long sequences of a one symbol and forces the symbol state back to a zero.
*/
public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISyncStateListener
public class LTRDecoder implements Listener<float[]>
{
private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
public static final float SAMPLES_PER_SYMBOL = 8000.0f / 300.0f;
protected static final float COARSE_TIMING_GAIN = 1.0f / 3.0f;
protected static final float MEDIUM_TIMING_GAIN = 1.0f / 4.0f;
protected static final float FINE_TIMING_GAIN = 1.0f / 5.0f;
private static final Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
//Slope calculation sumXX value is always the same for 27 evenly spaced samples
private static final double SLOPE_CALCULATION_SUM_XX = 1637.999992057681;
private static final float BAUD_LENGTH = 8000.0f / 300.0f;
private static final float SLOPE_THRESHOLD = 0.0048f;
private static final int MAX_ONES_SEQUENCE = 18;
private static final int OVERLAP = 26;
private static final int POST_TRANSITION_SAMPLES_TO_SKIP = 26;
private static final int SLOPE_CALCULATION_LENGTH = 27;
private static final int SYMBOL_TRANSITION_IDEAL_MIN = 11;
private static final int SYMBOL_TRANSITION_IDEAL_MAX = 16;
private static float[] sLowPassFilterCoefficients;
private boolean mSymbol = false;
private float[] mResidual = new float[OVERLAP];
private float mBaudCounter = 0f;
private float mMaxSlope = 0;
private int mExcessiveOneSequenceCounter = 0;
private Listener<boolean[]> mSymbolListener;
static
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(8000)
.gridDensity(16)
.oddLength(true)
.passBandCutoff(300)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(500)
.stopBandAmplitude(0.0)
.stopBandRipple(0.03) //Approximately 60 dB attenuation
.build();
.sampleRate(8000)
.gridDensity(16)
.oddLength(true)
.passBandCutoff(300)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(500)
.stopBandAmplitude(0.0)
.stopBandRipple(0.03) //Approximately 60 dB attenuation
.build();
try
{
@@ -87,143 +90,193 @@ public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISync
}
}
protected float mSymbolTimingGain = COARSE_TIMING_GAIN;
protected SampleBuffer mSampleBuffer;
protected ZeroCrossingErrorDetector mTimingErrorDetector = new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL);
protected SynchronizationMonitor mSynchronizationMonitor;
private IIRSinglePoleDCRemovalFilter mDCFilter = new IIRSinglePoleDCRemovalFilter(0.99999f);
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
private MessageFramer mMessageFramer;
private boolean mSampleDecision;
/**
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder
*
* @param messageLength in symbols
* Constructs an instance
*/
public LTRDecoder(int messageLength)
public LTRDecoder()
{
this(messageLength, new SampleBuffer(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL));
}
/**
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder.
*
* @param messageLength in symbols
* @param sampleBuffer to use for storing sample decisions
* @param detector for symbol timing errors
* Registers the listener to receive decoded symbols.
* @param listener to register
*/
public LTRDecoder(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector)
public void setListener(Listener<boolean[]> listener)
{
mSynchronizationMonitor = new SynchronizationMonitor(messageLength);
mSynchronizationMonitor.setListener(this);
mTimingErrorDetector = detector;
mSampleBuffer = sampleBuffer;
mSampleBuffer.setTimingGain(mSymbolTimingGain);
mSymbolListener = listener;
}
/**
* Implements the ISyncDetectedListener interface to be notified of message sync detection events.
*
* This allows the internal timing error detector to adjust symbol timing error gain levels according to the
* message synchronization state to quickly adjust to initial signal streams or to reduce gain levels once
* synchronization has been achieved.
* Processes the demodulated 8 kHz audio samples to extract the 300-baud LTR signalling and delivers the decoded
* symbol array to the registered listener.
* @param samples to demodulate.
*/
@Override
public void syncDetected(int bitErrors)
public void receive(float[] samples)
{
mSynchronizationMonitor.syncDetected(bitErrors);
}
@Override
public void syncLost(int bitsProcessed)
{
//no-op
}
/**
* Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
* greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
* adjusted and then each of the symbols are decoded using a simple majority decision.
*
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
*/
@Override
public void receive(float[] buffer)
{
float[] dcFiltered = mDCFilter.filter(buffer);
float[] lowPassFiltered = mLowPassFilter.filter(dcFiltered);
for(float sample : lowPassFiltered)
if(mSymbolListener != null)
{
mSampleDecision = sample > 0.0;
float[] filtered = mLowPassFilter.filter(samples);
mSampleBuffer.receive(mSampleDecision);
mTimingErrorDetector.receive(mSampleDecision);
int timingAdjust = 0;
int samplesToSkip = 0;
if(mSampleBuffer.hasSymbol())
float[] buffer = new float[filtered.length + OVERLAP];
boolean[] symbols = new boolean[(int)((filtered.length + mBaudCounter) / BAUD_LENGTH)];
int symbolPointer = 0;
float slope = 0;
try
{
if(mMessageFramer != null)
System.arraycopy(mResidual, 0, buffer, 0, mResidual.length);
System.arraycopy(filtered, 0, buffer, mResidual.length, filtered.length);
System.arraycopy(filtered, filtered.length - OVERLAP, mResidual, 0, OVERLAP);
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
{
mMessageFramer.process(mSampleBuffer.getSymbol());
//Don't calculate slope if we're within the baud period following last symbol transition
if(samplesToSkip > 0)
{
samplesToSkip--;
}
else
{
slope = calculateSlope(buffer, bufferPointer);
if(mSymbol)
{
//Loop for crest of slope at lowest point below the negative slope threshold for transition
if(slope > mMaxSlope && mMaxSlope < -SLOPE_THRESHOLD)
{
mSymbol = false;
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 16 samples
if(mBaudCounter < SYMBOL_TRANSITION_IDEAL_MIN)
{
timingAdjust = 1;
}
else if(mBaudCounter > SYMBOL_TRANSITION_IDEAL_MAX)
{
timingAdjust = -1;
}
else
{
timingAdjust = 0;
}
mBaudCounter += timingAdjust;
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
}
else if(slope < mMaxSlope)
{
mMaxSlope = slope;
}
}
else
{
//Loop for crest of slope at the highest point above the positive threshold
if(slope < mMaxSlope && mMaxSlope > SLOPE_THRESHOLD)
{
mSymbol = true;
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 16 samples
if(mBaudCounter < SYMBOL_TRANSITION_IDEAL_MIN)
{
timingAdjust = 1;
}
else if(mBaudCounter > SYMBOL_TRANSITION_IDEAL_MAX)
{
timingAdjust = -1;
}
else
{
timingAdjust = 0;
}
mBaudCounter += timingAdjust;
samplesToSkip = 26 + timingAdjust;
}
else if(slope > mMaxSlope)
{
mMaxSlope = slope;
}
}
}
mBaudCounter++;
if(mBaudCounter > BAUD_LENGTH)
{
//Expand symbols array length if needed to account for symbol timing adjustments
if(symbolPointer >= symbols.length)
{
symbols = Arrays.copyOf(symbols, symbols.length + 1);
}
symbols[symbolPointer++] = mSymbol;
mBaudCounter -= BAUD_LENGTH;
//Check for a continuous string of ones and flip the symbol state to zero.
if(mSymbol)
{
mExcessiveOneSequenceCounter++;
if(mExcessiveOneSequenceCounter > MAX_ONES_SEQUENCE) //Verify that this is the right threshold for a string of ones
{
mSymbol = false;
mMaxSlope = -1f;
mExcessiveOneSequenceCounter = 0;
}
}
else
{
mExcessiveOneSequenceCounter = 0;
}
}
}
}
catch(Exception e)
{
mLog.warn("Unexpected error while processing LTR samples", e);
}
mSampleBuffer.resetAndAdjust(-mTimingErrorDetector.getError());
mSynchronizationMonitor.increment();
//Truncate the symbols array if we didn't fill it completely, due to symbol timing adjustments.
if(symbolPointer != symbols.length)
{
mSymbolListener.receive(Arrays.copyOf(symbols, symbolPointer));
}
else
{
mSymbolListener.receive(symbols);
}
}
}
/**
* Registers a listener to receive decoded LTR symbols.
*
* @param messageFramer to receive symbols.
* Calculates the slope of the 27 samples in the values array starting at the specified offset.
* @param samples to load from
* @param offset to the sample start for the slope calculation
*/
public void setMessageFramer(MessageFramer messageFramer)
public float calculateSlope(float[] samples, int offset)
{
mMessageFramer = messageFramer;
}
/** mean of accumulated x & y values, used in updating formulas */
double xbar = 0;
double ybar = samples[offset];
/**
* Removes the symbol listener from receiving decoded LTR symbols.
*/
public void removeListener()
{
mMessageFramer = null;
}
double sumXY = 0;
double fact1, dx, dy;
/**
* Implements the ISyncStateListener interface to receive synchronization state events and adjust the symbol timing
* error gain levels on the internal timing error detector.
*
* Gain levels are defined relative to a unit gain of 1.0 over a number of symbol periods. LTR uses an initial
* ramp-up symbol reversal pattern of 0101 which provides three zero crossing detection opportunities for the timing
* error detector, therefore we react to a COARSE gain state to average timing error over 3 symbols.
*
* @param syncState from an external synchronization state monitor
*/
@Override
public void setSyncState(SyncState syncState)
{
switch(syncState)
for(int x = 1; x < SLOPE_CALCULATION_LENGTH; x++)
{
case FINE:
mSymbolTimingGain = FINE_TIMING_GAIN;
break;
case MEDIUM:
mSymbolTimingGain = MEDIUM_TIMING_GAIN;
break;
case COARSE:
mSymbolTimingGain = COARSE_TIMING_GAIN;
break;
default:
throw new IllegalArgumentException("Unrecognized sync state level: " + syncState.name());
fact1 = 1.0f + x;
dx = x - xbar;
dy = samples[offset + x] - ybar;
sumXY += dx * dy * (x / (1.0f + x));
xbar += dx / fact1;
ybar += dy / fact1;
}
mSampleBuffer.setTimingGain(mSymbolTimingGain);
return (float)(sumXY / SLOPE_CALCULATION_SUM_XX);
}
}

View File

@@ -1,40 +1,30 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.dsp.fsk;
public class LTRDecoderInstrumented extends LTRDecoder
{
/**
* Instrumented extension to LTRDecoder
*
* @param messageLength in symbols
*/
public LTRDecoderInstrumented(int messageLength)
public LTRDecoderInstrumented()
{
super(messageLength, new SampleBufferInstrumented(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
new ZeroCrossingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL));
}
public SampleBufferInstrumented getSampleBuffer()
{
return (SampleBufferInstrumented)mSampleBuffer;
}
public ZeroCrossingErrorDetectorInstrumented getErrorDetector()
{
return (ZeroCrossingErrorDetectorInstrumented)mTimingErrorDetector;
super();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,6 +30,7 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
private IPhaseLockedLoop mPLL;
private Complex mReceivedSample = new Complex(0, 0);
private Listener<T> mSymbolListener;
private boolean mRunning;
/**
* Abstract Phase Shift Keyed (PSK) demodulator
@@ -42,6 +43,31 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
mPLL = phaseLockedLoop;
}
/**
* Starts this decoder and sets the running flag to true
*/
public void start()
{
mRunning = true;
}
/**
* Stops this decoder and sets the running flag to false.
*/
public void stop()
{
mRunning = false;
}
/**
* Indicates if this demodulator is running and capable of processing samples
* @return true if running
*/
public boolean isRunning()
{
return mRunning;
}
/**
* Registers the listener to receive symbol decisions from this demodulator
*/
@@ -90,7 +116,10 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
for(int x = 0; x < i.length; x++)
{
receive(i[x], q[x]);
if(isRunning())
{
receive(i[x], q[x]);
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.edac;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
/**
* Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
* <p>
* See ETSI TS 102 361-1 B.2.2.1 and B.2.2.2
*/
public class BPTC_16_2
{
private static final int[] DEINTERLEAVE = new int[]{0, 24, 1, 25, 2, 26, 3, 27, 4, 28, 5, 29, 6, 30, 7, 31, 8, 16,
9, 17, 10, 18, 11, 19, 12, 20, 13, 21, 14, 22, 15, 23};
/**
* Unscramble and perform FEC checks per paragraph B.2.2.1 for Non-Reverse Channel Single Burst
*
* @param message with 32 interleaved bits.
* @return descrambled and error checked message or null if the process fails or there are too many errors.
*/
public static CorrectedBinaryMessage decodeShortBurst(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = deinterleave(message);
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
{
return null;
}
//Check for even parity. Bits 0-15 should be the same as bits 16-31.
for(int x = 0; x < 16; x++)
{
if(deinterleaved.get(x) ^ deinterleaved.get(x + 16))
{
return null;
}
}
return deinterleaved;
}
/**
* Unscramble and perform FEC checks per paragraph B.2.2.2 for Reverse Channel Single Burst
*
* @param binaryMessage with 32 interleaved bits.
* @return descrambled and error checked message or null if the process fails or there are too many errors.
*/
public static CorrectedBinaryMessage decodeReverseChannel(CorrectedBinaryMessage message)
{
CorrectedBinaryMessage deinterleaved = deinterleave(message);
System.out.println(" DEINTER: " + deinterleaved.toHexString());
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
System.out.println(" DECODED: " + deinterleaved.toHexString());
System.out.println("FEC:" + fec);
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
{
return null;
}
//Check for odd parity. Bits 0-15 should be opposite of bits 16-31.
for(int x = 0; x < 16; x++)
{
if(deinterleaved.get(x) == deinterleaved.get(x + 16))
{
return null;
}
}
return deinterleaved;
}
/**
* Performs deinterleave of the interleaved message.
*
* @param original to deinterleave
* @return deinterleaved message
*/
public static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage original)
{
CorrectedBinaryMessage delinterleaved = new CorrectedBinaryMessage(32);
for(int x = 0; x < 32; x++)
{
if(original.get(x))
{
delinterleaved.set(DEINTERLEAVE[x]);
}
}
return delinterleaved;
}
public static void main(String[] args)
{
String[] msgs = new String[]{"05030A03", "35003A00", "1C6D2C9E"};
for(String msg : msgs)
{
CorrectedBinaryMessage original = new CorrectedBinaryMessage(BinaryMessage.loadHex(msg));
System.out.println("ORIGINAL: " + original.toHexString());
CorrectedBinaryMessage decoded = decodeReverseChannel(original);
System.out.println("-------------------------");
}
}
}

View File

@@ -1,24 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.edac;
import io.github.dsheirer.message.MessageDirection;
import java.util.BitSet;
/**
@@ -98,6 +98,14 @@ public class CRCLTR
return CRC.FAILED_CRC;
}
public static String getCRCReason(BitSet msg, MessageDirection direction)
{
int calculated = getCalculatedChecksum( msg );
int transmitted = getTransmittedChecksum( msg );
return "CALC: " + Integer.toHexString(calculated).toUpperCase() +
" TRANS: " + Integer.toHexString(transmitted).toUpperCase();
}
public static byte[] getChecks()
{

View File

@@ -1,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
@@ -226,17 +226,19 @@ public class CRCUtil
{
mLog.debug("Starting");
//DMR message
// String raw = "100110010001000000001001000000001111011011010000011001010000000000000000000000001100001000001011";
String raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101001001111010001110";
mLog.debug(raw);
raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101000000000000000000";
BinaryMessage message = BinaryMessage.load(raw);
mLog.debug(message.toString());
long poly = 0x13l;
long[] checksums = generate(32, 4, poly, 0, true);
long polynomial = 0x11021l;
decode(message, 0, 80, polynomial, 16);
mLog.debug(message.toString());
mLog.debug("Finished");
StringBuilder sb = new StringBuilder();
sb.append("private static int[] CHECKSUMS = new int[]{");
for(long checksum: checksums)
{
sb.append("0x").append(Long.toHexString(checksum).toUpperCase());
sb.append(",");
}
sb.append("};");
System.out.println("Checksums:\n" + sb);
}
}

View File

@@ -1,3 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.edac;
import io.github.dsheirer.bits.BinaryMessage;
@@ -5,27 +24,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* -----------------------------------------------------------------------
* Galois24 decoder based on Hank Wallace's tutorial/algorithm located at:
* http://www.aqdi.com/golay.htm
******************************************************************************/
/**
* Galois 24/12/7 decoder
*/
@@ -160,4 +158,17 @@ public class Golay24
return (checksum ^ calculated);
}
public static void main(String[] args)
{
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20"));
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0"));
CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00"));
System.out.println("M:" + bm.toHexString());
int a = Golay24.checkAndCorrect(bm, 0);
System.out.println("M:" + bm.toHexString());
System.out.println("A:" + a);
}
}

View File

@@ -1,35 +1,61 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
/**
* Implements a message filter that passes (true) all messages
* Implements an all-pass filter that automatically passes/allows any presented object.
*
* A key type of String is used, but that makes no difference in how this filter functions.
*/
@SuppressWarnings( "rawtypes" )
public class AllPassFilter<T> extends Filter<T>
public class AllPassFilter<T> extends Filter<T, String>
{
public AllPassFilter()
private static final String KEY = "Other/Unlisted";
private final AllPassKeyExtractor mKeyExtractor = new AllPassKeyExtractor();
/**
* Constructor
* @param name to display for this filter
*/
public AllPassFilter(String name)
{
super(name);
add(new FilterElement<>(KEY));
}
@Override
public Function getKeyExtractor()
{
super( "Allow All Messages" );
return mKeyExtractor;
}
@Override
public boolean passes( T t )
{
return true;
}
@Override
public boolean canProcess( T t )
{
return true;
}
@Override
@SuppressWarnings( "unchecked" )
public List getFilterElements()
{
return Collections.EMPTY_LIST;
}
/**
* Key extractor that always returns the same (String)key.
*/
public class AllPassKeyExtractor implements Function<T,String>
{
@Override
public String apply(T t)
{
return KEY;
}
}
}

View File

@@ -1,55 +1,206 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class Filter<T> implements IFilter<T>
/**
* Base filter class that uses an extractor function to extract a filter key (K) from an object of type T and then
* lookup the filter element (rule) indicating if objects of this type are enabled.
*
* @param <T> object type that this filter can process.
* @param <K> key type used to lookup filter elements
*/
public abstract class Filter<T,K> implements IFilter<T>
{
protected String mName;
protected boolean mEnabled = true;
public Filter( String name )
{
mName = name;
}
private static final Logger LOG = LoggerFactory.getLogger(Filter.class);
private String mName;
private Map<K, FilterElement<K>> mFilterElementMap = new HashMap<>();
private IFilterChangeListener mFilterChangeListener;
/**
* Indicates if this filter is enabled or disabled
*/
public boolean isEnabled()
{
return mEnabled;
}
/**
* Constructs an instance
* @param name of this filter
*/
public Filter(String name)
{
mName = name;
}
/**
* Enables (true) or disables (false) this filter. An enabled filter will
* evaluate messages and a disabled filter will return false on all message
* test methods.
*/
public void setEnabled( boolean enabled )
{
mEnabled = enabled;
}
/**
* List of filter elements managed by this filter
*/
public abstract List<FilterElement<?>> getFilterElements();
/**
* Indicates if this filter contains a filter element matching the key value.
* @param key to check
* @return true if this filter has a matching filter element.
*/
protected boolean hasKey(K key)
{
return mFilterElementMap.containsKey(key);
}
/**
* Name of this filter
*/
public String getName()
{
return mName;
}
public void setName( String name )
{
mName = name;
}
public String toString()
{
return mName;
}
/**
* Key extractor function provided by the subclass.
* @return key extractor function
*/
public abstract Function<T,K> getKeyExtractor();
@Override
public boolean passes(T t)
{
K key = getKeyExtractor().apply(t);
if(mFilterElementMap.containsKey(key))
{
return mFilterElementMap.get(key).isEnabled();
}
return false;
}
/**
* Indicates if this filter can process the item T, meaning that it has a FilterElement that matches the key
* value for items of type T.
* @param t to test
* @return true if this filter can process item t
*/
@Override
public boolean canProcess(T t)
{
return hasKey(getKeyExtractor().apply(t));
}
@Override
public boolean isEnabled()
{
for(FilterElement filterElement: mFilterElementMap.values())
{
if(filterElement.isEnabled())
{
return true;
}
}
return false;
}
/**
* Adds the child filter element to this filter
* @param filterElement to add
*/
public void add(FilterElement<K> filterElement)
{
if(mFilterElementMap.containsKey(filterElement.getElement()))
{
LOG.warn("Filter element for key [" + filterElement.getElement() + "] named [" + filterElement.getName() +
"] already exists - overwriting existing value");
}
mFilterElementMap.put(filterElement.getElement(), filterElement);
filterElement.register(mFilterChangeListener);
}
/**
* List of filter elements managed by this filter
*/
public final List<FilterElement<K>> getFilterElements()
{
return new ArrayList<>(mFilterElementMap.values());
}
/**
* Name of this filter
*/
public String getName()
{
return mName;
}
/**
* Sets the name of this filter
*
* @param name to set
*/
public void setName(String name)
{
mName = name;
}
/**
* Pretty version or name of this filter.
*
* @return name
*/
public String toString()
{
return mName;
}
/**
* Count of enabled child filter elements.
*
* @return count
*/
@Override
public int getEnabledCount()
{
int count = 0;
for(FilterElement filterElement : mFilterElementMap.values())
{
if(filterElement.isEnabled())
{
count++;
}
}
return count;
}
/**
* Count of child filter elements
*
* @return count
*/
@Override
public int getElementCount()
{
return mFilterElementMap.size();
}
/**
* Registers a filter change listener on this filter and all filter elements.
* @param listener to be notified of any filter changes.
*/
@Override
public void register(IFilterChangeListener listener)
{
mFilterChangeListener = listener;
for(FilterElement child: mFilterElementMap.values())
{
child.register(listener);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import java.awt.Component;
import net.miginfocom.swing.MigLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
/**
* Filter editor
* @param <T> item type for editing
*/
public class FilterEditor<T> extends JFrame
{
private FilterEditorPanel<T> mEditorPanel;
/**
* Constructor
* @param title for the editor window frame
* @param owner to register the popup location
* @param filterSet to use initially
*/
public FilterEditor(String title, Component owner, FilterSet<T> filterSet)
{
if(filterSet == null)
{
throw new IllegalArgumentException("Unable to construct FilterEditor - FilterSet cannot be null");
}
setTitle(title);
setSize(600, 400);
setLocationRelativeTo(owner);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][]"));
mEditorPanel = new FilterEditorPanel<>(filterSet);
JScrollPane scroller = new JScrollPane(mEditorPanel);
scroller.setViewportView(mEditorPanel);
add(scroller, "wrap");
JButton close = new JButton("Close");
close.addActionListener(e -> dispose());
add(close);
}
/**
* Updates this editor with the filterset.
* @param filterSet to use in this editor.
*/
public void updateFilterSet(FilterSet<T> filterSet)
{
mEditorPanel.updateFilterSet(filterSet);
}
}

View File

@@ -1,100 +1,128 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import java.awt.Component;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.CellEditorListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreeSelectionModel;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.List;
/**
* Editor panel for managing the state of a filter set.
*
* @param <T> element type for the filter set.
*/
public class FilterEditorPanel<T> extends JPanel
{
private static final long serialVersionUID = 1L;
private JTree mTree;
private DefaultTreeModel mModel;
private FilterSet<T> mFilterSet;
/**
* Constructs an instance
* @param filterSet to manage
*/
public FilterEditorPanel(FilterSet<T> filterSet)
{
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
mFilterSet = filterSet;
init();
}
/**
* Initializes the panel and the tree model.
*/
private void init()
{
DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet);
mModel = new DefaultTreeModel(root);
addFilterSet(mFilterSet, root);
mTree = new JTree(mModel);
mTree.setShowsRootHandles(true);
mTree.addMouseListener(new MouseHandler());
mTree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
mTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
mTree.setCellRenderer(new EditorTreeCellRenderer());
mTree.setCellEditor(new FilterTreeNodeEditor());
mTree.setCellEditor(new FilterTreeCellEditor());
mTree.setEditable(true);
add(mTree);
}
/**
* Updates the filter set for this editor panel
* @param filterSet to use
*/
public void updateFilterSet(FilterSet<T> filterSet)
{
remove(mTree);
mFilterSet = filterSet;
init();
revalidate();
}
/**
* Adds the filter set as a child tree node to the parent.
* @param filterSet to add to the tree
* @param parent node for the filter set child tree node.
*/
private void addFilterSet(FilterSet<T> filterSet, DefaultMutableTreeNode parent)
{
List<IFilter<T>> filters = filterSet.getFilters();
/* sort the filters in alphabetical order by name */
filters.sort(Comparator.comparing(IFilter::getName));
for(IFilter<T> filter : filters)
{
DefaultMutableTreeNode child = new DefaultMutableTreeNode(filter);
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(filter);
mModel.insertNodeInto(childNode, parent, parent.getChildCount());
mModel.insertNodeInto(child, parent, parent.getChildCount());
if(filter instanceof FilterSet)
if(filter instanceof FilterSet childFilterSet)
{
addFilterSet((FilterSet<T>) filter, child);
addFilterSet(childFilterSet, childNode);
}
else if(filter instanceof Filter)
else if(filter instanceof Filter childFilter)
{
addFilter((Filter<T>) filter, child);
addFilter(childFilter, childNode);
}
}
}
private void addFilter(Filter<T> filter, DefaultMutableTreeNode parent)
/**
* Adds the filter as a child tree node to the parent.
*
* @param filter to add
* @param parent tree node
*/
private void addFilter(Filter filter, DefaultMutableTreeNode parent)
{
List<FilterElement<?>> elements = filter.getFilterElements();
@@ -103,123 +131,39 @@ public class FilterEditorPanel<T> extends JPanel
for(FilterElement<?> element : elements)
{
DefaultMutableTreeNode child = new DefaultMutableTreeNode(element);
mModel.insertNodeInto(child, parent, parent.getChildCount());
}
}
public class MouseHandler implements MouseListener
/**
* Accesses the tree's root tree node.
* @return root node.
*/
private DefaultMutableTreeNode getRoot()
{
public MouseHandler()
if(mTree.getModel().getRoot() instanceof DefaultMutableTreeNode root)
{
return root;
}
@Override
public void mouseClicked(MouseEvent event)
{
if(SwingUtilities.isRightMouseButton(event))
{
int row = mTree.getRowForLocation(event.getX(),
event.getY());
if(row != -1)
{
mTree.setSelectionRow(row);
Object selectedNode =
mTree.getLastSelectedPathComponent();
if(selectedNode instanceof DefaultMutableTreeNode)
{
final DefaultMutableTreeNode node =
(DefaultMutableTreeNode) selectedNode;
if(node.getChildCount() > 0)
{
JPopupMenu selectionMenu = new JPopupMenu();
JMenuItem selectAll = new JMenuItem("Select All");
selectAll.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
setFilterEnabled(mModel, node, true);
}
});
selectionMenu.add(selectAll);
JMenuItem deselectAll = new JMenuItem("Deselect All");
deselectAll.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
setFilterEnabled(mModel, node, false);
}
});
selectionMenu.add(deselectAll);
selectionMenu.show(mTree,
event.getX(),
event.getY());
}
}
}
}
}
@Override
public void mousePressed(MouseEvent e)
{
}
@Override
public void mouseReleased(MouseEvent e)
{
}
@Override
public void mouseEntered(MouseEvent e)
{
}
@Override
public void mouseExited(MouseEvent e)
{
}
return null;
}
/**
* Recursively sets the enabled state on all filter node children below the
* specified parent node.
* Recursively sets the enabled state on all filter node children below the specified parent node.
*
* @param model - model containing the tree nodes
* @param node - parent node
* @param enabled - true or false to enable or disable the filters
*/
private void setFilterEnabled(DefaultTreeModel model,
DefaultMutableTreeNode node,
boolean enabled)
private void setFilterEnabled(DefaultTreeModel model, DefaultMutableTreeNode node, boolean enabled)
{
Object obj = node.getUserObject();
if(obj instanceof FilterSet<?>)
if(obj instanceof FilterElement filterElement)
{
((FilterSet<?>) obj).setEnabled(enabled);
filterElement.setEnabled(enabled);
}
else if(obj instanceof Filter)
{
((Filter<?>) obj).setEnabled(enabled);
}
else if(obj instanceof FilterElement)
{
((FilterElement<?>) obj).setEnabled(enabled);
}
model.nodeChanged(node);
/* Recursively set the children of this node */
Enumeration<?> children = node.children();
@@ -228,43 +172,123 @@ public class FilterEditorPanel<T> extends JPanel
{
Object child = children.nextElement();
if(child instanceof DefaultMutableTreeNode)
if(child instanceof DefaultMutableTreeNode childNode)
{
setFilterEnabled(model, (DefaultMutableTreeNode) child, enabled);
setFilterEnabled(model, childNode, enabled);
}
}
model.nodeChanged(node);
}
/**
* Recursively searches the tree to find the tree node containing the specified filter element.
*
* @param node to start search
* @param element to find
*/
private DefaultMutableTreeNode findFilterElementNode(DefaultMutableTreeNode node, FilterElement<?> element)
{
if(node != null)
{
if(node.getUserObject() instanceof FilterElement<?> selfFilterElement && selfFilterElement.equals(element))
{
return node;
}
for(int x = 0; x < node.getChildCount(); x++)
{
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
{
DefaultMutableTreeNode found = findFilterElementNode(child, element);
if(found != null)
{
return found;
}
}
}
}
return null;
}
/**
* Updates the display value for the specified node and all parents in the nodes parentage.
* @param node to update
*/
private void updateParentage(DefaultMutableTreeNode node)
{
mModel.nodeChanged(node);
TreeNode parent = node.getParent();
while(parent != null)
{
mModel.nodeChanged(parent);
parent = parent.getParent();
}
}
/**
* Recursively searches the tree to find the tree node containing the specified filter.
*
* @param node to start search
* @param filter to find
*/
private DefaultMutableTreeNode findFilterNode(DefaultMutableTreeNode node, IFilter<?> filter)
{
if(node != null)
{
if(node.getUserObject() instanceof IFilter<?> selfFilter && selfFilter.equals(filter))
{
return node;
}
for(int x = 0; x < node.getChildCount(); x++)
{
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
{
DefaultMutableTreeNode found = findFilterNode(child, filter);
if(found != null)
{
return found;
}
}
}
}
else
{
System.out.println("Can't evaluate - node is null");
}
return null;
}
/**
* Custom cell renderer
*/
public class EditorTreeCellRenderer extends DefaultTreeCellRenderer
{
private static final long serialVersionUID = 1L;
public Component getTreeCellRendererComponent(JTree tree,
Object treeNode,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus)
public Component getTreeCellRendererComponent(JTree tree, Object treeNode, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus)
{
if(treeNode instanceof DefaultMutableTreeNode)
{
Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject();
JCheckBox checkBox = null;
if(userObject instanceof IFilter)
if(userObject instanceof IFilter filter)
{
IFilter<?> filter = (IFilter<?>) userObject;
checkBox = new JCheckBox(filter.getName());
checkBox = new FilterCheckBox(filter);
checkBox.setSelected(filter.isEnabled());
}
else if(userObject instanceof FilterElement)
else if(userObject instanceof FilterElement element)
{
FilterElement<?> element = (FilterElement<?>) userObject;
checkBox = new JCheckBox(element.getName());
checkBox = new FilterElementCheckBox(element);
checkBox.setSelected(element.isEnabled());
}
@@ -285,95 +309,110 @@ public class FilterEditorPanel<T> extends JPanel
}
}
return super.getTreeCellRendererComponent(tree, treeNode, selected,
expanded, leaf, row, hasFocus);
return super.getTreeCellRendererComponent(tree, treeNode, selected, expanded, leaf, row, hasFocus);
}
}
/**
* Check box used in filter tree nodes
*/
public class FilterCheckBox extends JCheckBox
{
private static final long serialVersionUID = 1L;
private IFilter<?> mFilter;
private IFilter<T> mFilter;
public FilterCheckBox(IFilter<T> filter)
/**
* Constructs an instance
*
* @param filter
*/
public FilterCheckBox(IFilter<?> filter)
{
super(filter.getName());
mFilter = filter;
setSelected(mFilter.isEnabled());
addItemListener(e -> {
DefaultMutableTreeNode selfNode = findFilterNode(getRoot(), mFilter);
addItemListener(new ItemListener()
{
@Override
public void itemStateChanged(ItemEvent e)
if(selfNode != null)
{
mFilter.setEnabled(FilterCheckBox.this.isSelected());
setFilterEnabled(mModel, selfNode, FilterCheckBox.this.isSelected());
updateLabel();
updateParentage(selfNode);
}
});
}
@Override
public void setSelected(boolean b)
{
super.setSelected(b);
updateLabel();
}
public void updateLabel()
{
setText(mFilter.getName() + " (" + mFilter.getEnabledCount() + "/" + mFilter.getElementCount() + ")");
}
}
/**
* Check box used in filter element tree nodes
*/
public class FilterElementCheckBox extends JCheckBox
{
private static final long serialVersionUID = 1L;
private FilterElement<?> mFilter;
/**
* Constructs an instance
*
* @param filter element
*/
public FilterElementCheckBox(FilterElement<?> filter)
{
super(filter.getName());
mFilter = filter;
setSelected(mFilter.isEnabled());
addItemListener(new ItemListener()
{
@Override
public void itemStateChanged(ItemEvent arg0)
addItemListener(arg0 -> {
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
DefaultMutableTreeNode self = findFilterElementNode(getRoot(), mFilter);
if(self != null)
{
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
updateParentage(self);
}
});
}
}
public class FilterTreeNodeEditor implements TreeCellEditor
/**
* Editor for filter tree nodes
*/
public class FilterTreeCellEditor implements TreeCellEditor
{
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Component getTreeCellEditorComponent(JTree tree, Object node,
boolean isSelected, boolean expanded, boolean leaf, int row)
public Component getTreeCellEditorComponent(JTree tree, Object node, boolean isSelected, boolean expanded, boolean leaf, int row)
{
if(node instanceof DefaultMutableTreeNode)
if(node instanceof DefaultMutableTreeNode mutableTreeNode)
{
Object userObject = ((DefaultMutableTreeNode) node).getUserObject();
Object userObject = mutableTreeNode.getUserObject();
if(userObject instanceof IFilter)
if(userObject instanceof IFilter filter)
{
return new FilterCheckBox((IFilter) userObject);
return new FilterCheckBox(filter);
}
else if(userObject instanceof FilterElement)
else if(userObject instanceof FilterElement filterElement)
{
return new FilterElementCheckBox((FilterElement<?>) userObject);
return new FilterElementCheckBox(filterElement);
}
}
return new JLabel(node.toString());
}
@Override
public void addCellEditorListener(CellEditorListener l)
{
}
public void addCellEditorListener(CellEditorListener l) {}
@Override
public void cancelCellEditing()
{
}
public void cancelCellEditing() {}
@Override
public Object getCellEditorValue()
@@ -388,9 +427,7 @@ public class FilterEditorPanel<T> extends JPanel
}
@Override
public void removeCellEditorListener(CellEditorListener l)
{
}
public void removeCellEditorListener(CellEditorListener l) {}
@Override
public boolean shouldSelectCell(EventObject anEvent)

View File

@@ -1,55 +1,148 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
/**
* Filter element provides filtering and enabled state tracking for an individual filterable element.
*
* @param <T> element to track and filter.
*/
public class FilterElement<T> implements Comparable<FilterElement<T>>
{
private boolean mEnabled;
private T mElement;
public FilterElement( T element, boolean enabled )
{
mElement = element;
mEnabled = enabled;
}
public FilterElement( T t )
{
this( t, true );
}
public T getElement()
{
return mElement;
}
public String toString()
{
return getName();
}
public boolean isEnabled()
{
return mEnabled;
}
public void setEnabled( boolean enabled )
{
mEnabled = enabled;
}
public String getName()
{
return mElement.toString();
}
private boolean mEnabled;
private final T mElement;
private IFilterChangeListener mFilterChangeListener;
@Override
public int compareTo( FilterElement<T> other )
/**
* Constructs an instance
*
* @param element to track
* @param enabled enabled state of this filter element
*/
public FilterElement(T element, boolean enabled)
{
return toString().compareTo( other.toString() );
mElement = element;
mEnabled = enabled;
}
/**
* Constructs an instance with a default state of enabled (true).
*
* @param t element to track
*/
public FilterElement(T t)
{
this(t, true);
}
/**
* Registers the listener with each child filter element.
*
* @param listener to register
*/
public void register(IFilterChangeListener listener)
{
mFilterChangeListener = listener;
}
/**
* Element being filtered.
*
* @return element
*/
public T getElement()
{
return mElement;
}
/**
* Name or display string for the element.
*
* @return string version of the element.
*/
public String toString()
{
return getName();
}
/**
* Indicates if this filter is enabled indicating that elements of this type will pass this filter.
*
* @return enabled state.
*/
public boolean isEnabled()
{
return mEnabled;
}
/**
* Sets the enabled state of this filter.
*
* @param enabled true to allow this filter's element type to pass the filter.
*/
public void setEnabled(boolean enabled)
{
mEnabled = enabled;
if(mFilterChangeListener != null)
{
mFilterChangeListener.filterChanged();
}
}
/**
* Name of the tracked element.
*
* @return name
*/
public String getName()
{
return mElement.toString();
}
/**
* Implements compareto interface for ordering of filter elements.
*
* @param other the object to be compared.
* @return comparison value
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof FilterElement)) return false;
return compareTo((FilterElement) o) == 0;
public int compareTo(FilterElement<T> other)
{
return toString().compareTo(other.toString());
}
/**
* Implement equals interface for comparison of filter elements.
*
* @param o to compare
* @return comparison
*/
@Override
public boolean equals(Object o)
{
if(o instanceof FilterElement fe)
{
return compareTo(fe) == 0;
}
return false;
}
}

View File

@@ -1,108 +1,239 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
import io.github.dsheirer.log.LoggingSuppressor;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Filter set is a parent container for filters.
*
* @param <T> type of element tracked/filtered by this filter set.
*/
public class FilterSet<T> implements IFilter<T>
{
protected List<IFilter<T>> mFilters = new ArrayList<IFilter<T>>();
protected boolean mEnabled = true;
protected String mName;
private static final Logger LOGGER = LoggerFactory.getLogger(FilterSet.class);
private static LoggingSuppressor sLoggingSuppressor = new LoggingSuppressor(LOGGER);
private List<IFilter<T>> mFilters = new ArrayList<>();
private String mName;
private IFilterChangeListener mFilterChangeListener;
public FilterSet( String name )
{
mName = name;
}
public FilterSet()
{
mName = "Message Filter";
}
public FilterSet(IFilter<T> filter)
/**
* Constructs an instance of a filter set.
*
* @param name of this filter set.
*/
public FilterSet(String name)
{
mName = name;
}
/**
* Constructs an instance with a single filter using the default 'Filters' name.
*
* @param filter for this filter set.
*/
public FilterSet(IFilter<T> filter)
{
this("Filters");
addFilter(filter);
}
public String getName()
{
return mName;
}
public void setName( String name )
{
mName = name;
}
public String toString()
{
return getName();
}
@Override
public boolean passes( T t )
{
if( mEnabled )
{
for( IFilter<T> filter: mFilters )
{
// Make sure to loop through all filters.
// Only return if true or all filters have been evaluated.
if( filter.canProcess( t ) && filter.passes( t ))
{
return true;
}
}
}
return false;
}
@Override
public boolean canProcess( T t )
{
if( mEnabled )
/**
* Registers the listener with each child filter element.
* @param listener to register
*/
public void register(IFilterChangeListener listener)
{
mFilterChangeListener = listener;
for(IFilter child: mFilters)
{
for( IFilter<T> filter: mFilters )
child.register(listener);
}
}
/**
* Name of this filter set.
*
* @return name
*/
public String getName()
{
return mName;
}
/**
* Sets the name of this filter set.
*
* @param name to set
*/
public void setName(String name)
{
mName = name;
}
/**
* Name for this filter set.
*
* @return name
*/
public String toString()
{
return getName();
}
/**
* Indicates if the element passes at least one of the child filters in this filter set.
*
* @param t to evaluate
* @return true if the argument passes a child filter.
*/
@Override
public boolean passes(T t)
{
for(IFilter<T> filter : mFilters)
{
//Stop at the first filter that says that it can process the message type.
if(filter.canProcess(t))
{
if( filter.canProcess( t ) )
{
return true;
}
return filter.passes(t);
}
}
return false;
}
}
/**
* Indicates if this filter set contains at least one filter that can evaluate the type of argument that is presented.
*
* @param t element to evaluate
* @return true if this filter set can evaluate objects of the argument's type.
*/
@Override
public boolean canProcess(T t)
{
for(IFilter<T> filter : mFilters)
{
if(filter.canProcess(t))
{
return true;
}
}
sLoggingSuppressor.info(t.getClass().getSimpleName(), 1, "FilterSet [" +
this.getClass().getSimpleName() + "] has no filter element for items of type [" +
t.getClass().getSimpleName() + "]");
return false;
}
/**
* Child filters
*
* @return filters
*/
public List<IFilter<T>> getFilters()
{
return mFilters;
}
public void addFilters( List<IFilter<T>> filters )
{
mFilters.addAll( filters );
}
public void addFilter( IFilter<T> filter )
{
mFilters.add( filter );
}
public void removeFilter( IFilter<T> filter )
{
mFilters.remove( filter );
return new ArrayList<>(mFilters);
}
@Override
public boolean isEnabled()
{
return mEnabled;
}
/**
* Adds a list of child filters to this filter set.
* @param filters to add
*/
public void addFilters(List<IFilter<T>> filters)
{
for(IFilter<T> filter: filters)
{
addFilter(filter);
}
}
/**
* Adds the child filter to this filter set.
* @param filter to add
*/
public void addFilter(IFilter<T> filter)
{
if(filter instanceof Filter<?,?> filterInstance && filterInstance.getKeyExtractor() == null)
{
LOGGER.warn("Filter [" + filter.getClass() + "] has a null key extractor");
}
mFilters.add(filter);
filter.register(mFilterChangeListener);
}
/**
* Indicates if any of the child filters contain at least one filter element that is enabled.
* @return enabled state
*/
@Override
public void setEnabled( boolean enabled )
{
mEnabled = enabled;
}
public boolean isEnabled()
{
for(IFilter filter: mFilters)
{
if(filter.isEnabled())
{
return true;
}
}
return false;
}
/**
* Recursive count of enabled child filter elements.
* @return count
*/
@Override
public int getEnabledCount()
{
int count = 0;
for(IFilter filter: mFilters)
{
count += filter.getEnabledCount();
}
return count;
}
/**
* Recursive count of child filter elements.
* @return count
*/
@Override
public int getElementCount()
{
int count = 0;
for(IFilter filter: mFilters)
{
count += filter.getElementCount();
}
return count;
}
}

View File

@@ -1,36 +1,75 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.filter;
/**
* Filter interface
*
* @param <T> type of filter
*/
public interface IFilter<T>
{
/**
* Generic filter method.
*
* @param message - message to filter
* @return - true if the message passes the filter
*/
public boolean passes( T t );
/**
* Generic filter method.
*
* @param t - message to filter
* @return - true if the message passes the filter
*/
boolean passes(T t);
/**
* Indicates if the filter can process (filter) the object
*
* @param message - candidate message for filtering
*
* @return - true if the filter is capable of filtering the message
*/
public boolean canProcess( T t );
/**
* Indicates if the filter can process (filter) the object
*
* @param t - candidate message for filtering
* @return - true if the filter is capable of filtering the message
*/
boolean canProcess(T t);
/**
* Indicates if this filter is enabled to evaluate messages
*/
public boolean isEnabled();
/**
* Indicates if this filter is enabled to evaluate messages
*/
boolean isEnabled();
/**
* Sets the enabled state of the filter
*/
public void setEnabled( boolean enabled );
/**
* Display name for the filter
*/
public String getName();
/**
* Display name for the filter
*/
String getName();
/**
* (Recursive) count of enabled child filter elements.
*
* @return total enable count.
*/
int getEnabledCount();
/**
* (Recursive) count of total child filter elements.
*
* @return total count of child filter elements.
*/
int getElementCount();
/**
* Registers the listener to be notified when the enabled state of any child filter elements is updated.
*
* @param listener to be notified
*/
void register(IFilterChangeListener listener);
}

View File

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

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,30 +14,36 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
package io.github.dsheirer.filter;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.message.SyncLossMessage;
import java.util.function.Function;
import java.util.Collections;
import java.util.List;
public class SyncLossMessageFilter extends Filter<IMessage>
/**
* Filter for sync-loss messages
*/
public class SyncLossMessageFilter extends Filter<IMessage,String>
{
private static final String SYNC_LOSS_KEY = "Sync-Loss";
private KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public SyncLossMessageFilter()
{
super("Sync Loss");
super("Sync Loss Messages");
add(new FilterElement<>(SYNC_LOSS_KEY));
}
@Override
public boolean passes(IMessage message)
public Function<IMessage, String> getKeyExtractor()
{
return mEnabled && canProcess(message);
return mKeyExtractor;
}
@Override
@@ -47,9 +52,20 @@ public class SyncLossMessageFilter extends Filter<IMessage>
return message instanceof SyncLossMessage;
}
@Override
public List<FilterElement<?>> getFilterElements()
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
return Collections.EMPTY_LIST;
@Override
public String apply(IMessage message)
{
if(message instanceof SyncLossMessage)
{
return SYNC_LOSS_KEY;
}
return null;
}
}
}

View File

@@ -1,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
@@ -22,22 +22,9 @@ import io.github.dsheirer.source.ISourceEventProcessor;
import io.github.dsheirer.source.InvalidFrequencyException;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceException;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
@@ -49,27 +36,39 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
* Custom frequency controller for displaying or editing frequency values in MHz.
*/
public class JFrequencyControl extends JPanel implements ISourceEventProcessor
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(JFrequencyControl.class);
private List<ISourceEventProcessor> mProcessors = new ArrayList<>();
private Color mHighlightColor = Color.YELLOW;
private long mFrequency;
private Cursor mBlankCursor;
private HashMap<Integer,Digit> mDigits = new HashMap<Integer,Digit>();
/**
* Constructor
*/
public JFrequencyControl()
{
init();
}
/**
* Initializes the control/layout.
*/
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0", "[]0[]", ""));
@@ -104,19 +103,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
/**
* Create a blank cursor to use when the mouse is over the digits
*/
//Create an empty byte array
byte[] imageByte = new byte[0];
//Create image for cursor using empty array
Image cursorImage = Toolkit.getDefaultToolkit().createImage(imageByte);
mBlankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "cursor");
revalidate();
}
/**
* Enables or disables the control.
* @param enabled true if this component should be enabled, false otherwise
*/
@Override
public void setEnabled(boolean enabled)
{
@@ -157,11 +150,18 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
/**
* Current frequency value for the control.
* @return frequency
*/
public long getFrequency()
{
return mFrequency;
}
/**
* Updates the frequency value according to the manually changed digit value.
*/
private void updateFrequency()
{
long frequency = 0;
@@ -174,14 +174,15 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
mFrequency = frequency;
}
/**
* Fires a source changed event
* @throws SourceException of there's an error
*/
private void fireSourceChanged() throws SourceException
{
updateFrequency();
Iterator<ISourceEventProcessor> it = mProcessors.iterator();
SourceEvent event = SourceEvent.frequencyRequest(mFrequency);
while(it.hasNext())
{
it.next().process(event);
@@ -217,12 +218,21 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
mProcessors.remove(processor);
}
/**
* Custom single digit value editor.
*/
public class Digit extends JTextField
{
private static final long serialVersionUID = 1L;
private int mPower = 0;
private long mValue = 0;
/**
* Constructor
* @param position of the digit where a position of 1 is the ones unit of MHz and a position of -1 is the tenths
* unit of MHz.
* @throws ParseException if there is an error
*/
private Digit(int position) throws ParseException
{
super("0");
@@ -236,6 +246,11 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
this.addMouseWheelListener(listener);
}
/**
* Generates a custom tooltip for the current hover-over digit.
* @param power of the digit in units of 10
* @return custom tooptip.
*/
private static String getTooltip(int power)
{
StringBuilder sb = new StringBuilder();
@@ -288,23 +303,32 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
{
//Strip the digits higher than this one
long lower = frequency % (long)(FastMath.pow(10, mPower + 1));
//Set the value to int value of dividing by 10 to this power
long value = (long)(lower / (long)(FastMath.pow(10, mPower)));
set(value, fireChangeEvent);
}
/**
* Frequency value.
* @return frequency value.
*/
public long getFrequency()
{
return mValue * (long)FastMath.pow(10, mPower);
}
/**
* Increments the unit value and fires a change event
*/
public void increment()
{
increment(true);
}
/**
* Increments the unit value and fires a change event
* @param fireChangeEvent true to fire an event
*/
public void increment(boolean fireChangeEvent)
{
if(isEnabled())
@@ -313,11 +337,18 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
/**
* Decrements the unit value and fires a change event.
*/
public void decrement()
{
decrement(true);
}
/**
* Decrements the unit value and optionally fires a change event.
* @param fireChangeEvent set to true to fire an event
*/
public void decrement(boolean fireChangeEvent)
{
if(isEnabled())
@@ -349,7 +380,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
while(mValue < 0)
{
mValue += 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
@@ -363,7 +393,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
while(mValue > 9)
{
mValue -= 10;
Digit nextHigherDigit = mDigits.get(mPower + 1);
if(nextHigherDigit != null)
@@ -403,10 +432,8 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
if(se instanceof InvalidFrequencyException)
if(se instanceof InvalidFrequencyException ife)
{
InvalidFrequencyException ife = (InvalidFrequencyException)se;
JOptionPane.showMessageDialog(this, "Frequency [" + ife.getInvalidFrequency() +
"] exceeds the frequency limit [" + ife.getValidFrequency() + "] for this tuner.");
}
@@ -415,13 +442,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
setText(String.valueOf(mValue));
repaint();
}
private class Listener implements KeyListener,
MouseListener,
MouseWheelListener
/**
* Multiple Listener interface implementation
*/
private class Listener implements KeyListener, MouseListener, MouseWheelListener
{
@Override
public void keyReleased(KeyEvent e)
@@ -503,14 +530,9 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
@Override
public void keyPressed(KeyEvent e)
{
}
public void keyPressed(KeyEvent e) {}
@Override
public void keyTyped(KeyEvent e)
{
}
public void keyTyped(KeyEvent e) {}
@Override
public void mouseClicked(MouseEvent e)
@@ -528,21 +550,13 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
}
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e)
{
Digit.this.setBackground(mHighlightColor);
setCursor(mBlankCursor);
repaint();
}
@@ -550,9 +564,6 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
public void mouseExited(MouseEvent e)
{
Digit.this.setBackground(Color.WHITE);
setCursor(Cursor.getDefaultCursor());
repaint();
}

View File

@@ -459,6 +459,12 @@ public class DMRRecordingViewer extends VBox
if(mFindText == null)
{
mFindText = new TextField();
mFindText.setOnKeyPressed(event -> {
if(event.getCode().equals(KeyCode.ENTER))
{
getFindButton().fire();
}
});
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
}

View File

@@ -1,18 +1,21 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
@@ -58,7 +61,7 @@ public class LTRNetSampleBufferChart extends LineChart
mLTRNetDecoderInstrumented = decoder;
decoder.bufferCount.addListener(new BufferChangeListener());
decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
}
public void setBuffer(boolean[] buffer)
@@ -89,7 +92,7 @@ public class LTRNetSampleBufferChart extends LineChart
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
// setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
while(mSymbol.size() < 2)
{
@@ -115,29 +118,29 @@ public class LTRNetSampleBufferChart extends LineChart
mSymbolSamples.add(sample);
}
int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
mSamplePoints.get(0).setXValue(pointer1);
mSamplePoints.get(1).setXValue(pointer2);
// mSamplePoints.get(0).setXValue(pointer1);
// mSamplePoints.get(1).setXValue(pointer2);
float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
// float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
// float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
float start = pointer1 + samplesRemaining;
float end = start + samplesPerSymbol;
// float start = pointer1 + samplesRemaining;
// float end = start + samplesPerSymbol;
mSymbolSamples.get(0).setXValue(start);
mSymbolSamples.get(1).setXValue(end);
// mSymbolSamples.get(0).setXValue(start);
// mSymbolSamples.get(1).setXValue(end);
boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
// boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
// int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
// int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
mSymbol.get(0).setXValue(symbolStart);
mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
mSymbol.get(1).setXValue(symbolEnd);
mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
// mSymbol.get(0).setXValue(symbolStart);
// mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
// mSymbol.get(1).setXValue(symbolEnd);
// mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
}
}
@@ -152,20 +155,20 @@ public class LTRNetSampleBufferChart extends LineChart
mZeroCrossing.add(sample);
}
int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
reference--;
float start = reference - zeroCrossingIdeal;
float end = reference - detectedZeroCrossing;
mZeroCrossing.get(0).setXValue(start);
mZeroCrossing.get(1).setXValue(end);
// float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
// float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
//
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
// reference--;
//
// float start = reference - zeroCrossingIdeal;
// float end = reference - detectedZeroCrossing;
//
// mZeroCrossing.get(0).setXValue(start);
// mZeroCrossing.get(1).setXValue(end);
}
}
}

View File

@@ -1,18 +1,21 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.chart;
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
@@ -53,7 +56,7 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
setData(observableList);
mLTRNetDecoderInstrumented = decoder;
decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
decoder.bufferCount.addListener(new ChangeListener()
{
@Override
@@ -84,13 +87,13 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
private void updateBuffer()
{
boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
for(int x = 0; x < samples.length; x++)
{
Data<Number,Number> sample = mCurrentSamples.get(x);
sample.setYValue(samples[x] ? 0.7f : 0.3f);
}
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
//
// for(int x = 0; x < samples.length; x++)
// {
// Data<Number,Number> sample = mCurrentSamples.get(x);
// sample.setYValue(samples[x] ? 0.7f : 0.3f);
// }
}
public class ErrorChangeListener implements ChangeListener
@@ -98,35 +101,35 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
@Override
public void changed(ObservableValue observable, Object oldValue, Object newValue)
{
float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
for(int x = 0; x < samples.length; x++)
{
Data<Number,Number> sample = mPreviousSamples.get(x);
sample.setYValue(samples[x] ? -0.4f : -0.6f);
}
Data<Number,Number> ideal1 = mIdeal.get(0);
ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
Data<Number,Number> ideal2 = mIdeal.get(1);
ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
if(error == 0.0f)
{
Data<Number,Number> detected1 = mDetected.get(0);
detected1.setXValue(0.0f);
Data<Number,Number> detected2 = mDetected.get(1);
detected2.setXValue(0.0f);
}
else
{
Data<Number,Number> detected1 = mDetected.get(0);
detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
Data<Number,Number> detected2 = mDetected.get(1);
detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
}
// float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
//
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
//
// for(int x = 0; x < samples.length; x++)
// {
// Data<Number,Number> sample = mPreviousSamples.get(x);
// sample.setYValue(samples[x] ? -0.4f : -0.6f);
// }
//
// Data<Number,Number> ideal1 = mIdeal.get(0);
// ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
// Data<Number,Number> ideal2 = mIdeal.get(1);
// ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
//
// if(error == 0.0f)
// {
// Data<Number,Number> detected1 = mDetected.get(0);
// detected1.setXValue(0.0f);
// Data<Number,Number> detected2 = mDetected.get(1);
// detected2.setXValue(0.0f);
// }
// else
// {
// Data<Number,Number> detected1 = mDetected.get(0);
// detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
// Data<Number,Number> detected2 = mDetected.get(1);
// detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
// }
}
}
}

View File

@@ -1,18 +1,21 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.instrument.decoder;
import io.github.dsheirer.gui.instrument.chart.LTRNetSampleBufferChart;
@@ -142,8 +145,8 @@ public class LTRNetPane extends RealDecoderPane
{
if(mZeroCrossingErrorDetectorChart == null)
{
mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
// mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
// getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
}
return mZeroCrossingErrorDetectorChart;

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.playlist.alias;
@@ -37,6 +34,7 @@ import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.alias.id.radio.Radio;
@@ -63,6 +61,11 @@ import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.protocol.Protocol;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
@@ -106,12 +109,6 @@ import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* Editor for configuring individual aliases
*/
@@ -717,6 +714,7 @@ public class AliasItemEditor extends Editor<Alias>
Menu nbfmMenu = new ProtocolMenu(Protocol.NBFM);
nbfmMenu.getItems().add(new AddTalkgroupItem(Protocol.NBFM));
nbfmMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.NBFM));
nbfmMenu.getItems().add(new AddDcsItem());
Menu passportMenu = new ProtocolMenu(Protocol.PASSPORT);
passportMenu.getItems().add(new AddTalkgroupItem(Protocol.PASSPORT));
@@ -1281,6 +1279,24 @@ public class AliasItemEditor extends Editor<Alias>
}
}
/**
* Add Digital Coded Squelch (DCS) alias identifier menu item
*/
public class AddDcsItem extends MenuItem
{
public AddDcsItem()
{
super("Digital Coded Squelch (DCS)");
setOnAction(event -> {
Dcs dcs = new Dcs();
getIdentifiersList().getItems().add(dcs);
getIdentifiersList().getSelectionModel().select(dcs);
getIdentifiersList().scrollTo(dcs);
modifiedProperty().set(true);
});
}
}
/**
* Menu Item for adding a new protocol-specific Radio ID alias identifier
*/

View File

@@ -0,0 +1,98 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui.playlist.alias.identifier;
import io.github.dsheirer.alias.id.dcs.Dcs;
import io.github.dsheirer.module.decode.dcs.DCSCode;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Editor for Digital Coded Squelch (DCS) alias identifiers
*/
public class DcsEditor extends IdentifierEditor<Dcs>
{
private static final Logger mLog = LoggerFactory.getLogger(DcsEditor.class);
private ComboBox<DCSCode> mDCSCodeComboBox;
/**
* Constructs an instance
*/
public DcsEditor()
{
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
Label typeLabel = new Label("DCS Code");
GridPane.setConstraints(typeLabel, 0, 0);
gridPane.getChildren().add(typeLabel);
GridPane.setConstraints(getDCSCodeComboBox(), 1, 0);
gridPane.getChildren().add(getDCSCodeComboBox());
getChildren().add(gridPane);
}
@Override
public void setItem(Dcs item)
{
super.setItem(item);
if(item.isValid())
{
getDCSCodeComboBox().getSelectionModel().select(item.getDCSCode());
}
modifiedProperty().set(false);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
/**
* Combo-box loaded with DCS codes
* @return
*/
private ComboBox<DCSCode> getDCSCodeComboBox()
{
if(mDCSCodeComboBox == null)
{
mDCSCodeComboBox = new ComboBox<>();
mDCSCodeComboBox.getItems().addAll(DCSCode.STANDARD_CODES);
mDCSCodeComboBox.getItems().addAll(DCSCode.INVERTED_CODES);
mDCSCodeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
getItem().setDCSCode(getDCSCodeComboBox().getSelectionModel().getSelectedItem());
modifiedProperty().set(true);
});
}
return mDCSCodeComboBox;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,6 +35,8 @@ public class IdentifierEditorFactory
{
switch(type)
{
case DCS:
return new DcsEditor();
case ESN:
return new EsnEditor();
case LOJACK:

View File

@@ -85,7 +85,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
private ToggleSwitch mUseCompressedTalkgroupsToggle;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
private IntegerTextField mLogicalSlotNumberField;
private IntegerTextField mLogicalChannelNumberField;
private FrequencyField mDownlinkFrequencyField;
// private FrequencyField mUplinkFrequencyField;
private Button mAddTimeslotFrequencyButton;
@@ -172,7 +172,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
Label timeslotTableLabel = new Label("Logical Channel Number (LCN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies. LSN = Logical Slot Number");
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
gridPane.getChildren().add(timeslotTableLabel);
@@ -194,17 +194,13 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
editorBox.setAlignment(Pos.CENTER_LEFT);
editorBox.setSpacing(5);
Label lsnLabel = new Label("LSN");
editorBox.getChildren().addAll(lsnLabel,getLogicalSlotNumberField());
Label lcnLabel = new Label("LCN");
editorBox.getChildren().addAll(lcnLabel, getLogicalChannelNumberField());
Label downlinkLabel = new Label("Frequency (MHz)");
downlinkLabel.setPadding(new Insets(0,0,0,5));
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
// Label uplinkLabel = new Label("Uplink");
// uplinkLabel.setPadding(new Insets(0,0,0,5));
// editorBox.getChildren().addAll(uplinkLabel,getUplinkFrequencyField());
GridPane.setConstraints(editorBox, 0, row, 4, 1);
gridPane.getChildren().add(editorBox);
@@ -287,20 +283,22 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
mTimeslotFrequencyTable.setPrefHeight(100.0);
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LSN");
numberColumn.setPrefWidth(70);
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LCN");
numberColumn.setPrefWidth(75);
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
downlinkColumn.setPrefWidth(175);
downlinkColumn.setPrefWidth(150);
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
// TableColumn<TimeslotFrequency,Number> uplinkColumn = new TableColumn("Uplink (MHz)");
// uplinkColumn.setCellValueFactory(cellData -> cellData.getValue().getUplinkMHz());
// mTimeslotFrequencyTable.getColumns().addAll(uplinkColumn);
TableColumn<TimeslotFrequency,String> lsnColumn = new TableColumn("IDs (TS1/TS2)");
lsnColumn.setPrefWidth(225);
lsnColumn.setCellValueFactory(new PropertyValueFactory<>("logicalSlotNumbers"));
mTimeslotFrequencyTable.getColumns().addAll(lsnColumn);
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
@@ -317,20 +315,20 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
//Preserve the current modified flag state since setting values in the editor will change it.
boolean modified = modifiedProperty().get();
getLogicalSlotNumberField().setDisable(timeslot == null);
getLogicalChannelNumberField().setDisable(timeslot == null);
getDownlinkFrequencyField().setDisable(timeslot == null);
// getUplinkFrequencyField().setDisable(timeslot == null);
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
if(timeslot != null)
{
getLogicalSlotNumberField().set(timeslot.getNumber());
getLogicalChannelNumberField().set(timeslot.getNumber());
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
}
else
{
getLogicalSlotNumberField().set(0);
getLogicalChannelNumberField().set(0);
getDownlinkFrequencyField().set(0);
// getUplinkFrequencyField().set(0);
}
@@ -412,19 +410,19 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
return mDeleteTimeslotFrequencyButton;
}
private IntegerTextField getLogicalSlotNumberField()
private IntegerTextField getLogicalChannelNumberField()
{
if(mLogicalSlotNumberField == null)
if(mLogicalChannelNumberField == null)
{
mLogicalSlotNumberField = new IntegerTextField();
mLogicalSlotNumberField.setDisable(true);
mLogicalSlotNumberField.setPrefWidth(65);
mLogicalSlotNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
mLogicalChannelNumberField = new IntegerTextField();
mLogicalChannelNumberField.setDisable(true);
mLogicalChannelNumberField.setPrefWidth(65);
mLogicalChannelNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
if(selected != null)
{
Integer value = mLogicalSlotNumberField.get();
Integer value = mLogicalChannelNumberField.get();
if(value != null)
{
@@ -436,7 +434,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
});
}
return mLogicalSlotNumberField;
return mLogicalChannelNumberField;
}
private FrequencyField getDownlinkFrequencyField()
@@ -610,8 +608,8 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
getTimeslotTable().setDisable(config == null);
getAddTimeslotFrequencyButton().setDisable(config == null);
getDeleteTimeslotFrequencyButton().setDisable(true);
getLogicalSlotNumberField().set(0);
getLogicalSlotNumberField().setDisable(true);
getLogicalChannelNumberField().set(0);
getLogicalChannelNumberField().setDisable(true);
getDownlinkFrequencyField().set(0);
getDownlinkFrequencyField().setDisable(true);
// getUplinkFrequencyField().set(0);

View File

@@ -27,6 +27,7 @@ import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.analog.DecodeConfigAnalog;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
@@ -40,8 +41,6 @@ import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.manager.TunerManager;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
@@ -58,15 +57,15 @@ import javafx.scene.layout.GridPane;
import javafx.scene.text.TextAlignment;
import org.controlsfx.control.SegmentedButton;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Narrow-Band FM channel configuration editor
*/
public class NBFMConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(NBFMConfigurationEditor.class);
private TitledPane mAuxDecoderPane;
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
@@ -75,8 +74,9 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
private TextField mTalkgroupField;
private TextField mSquelchThresholdField;
private ToggleSwitch mSquelchAutoTrackSwitch;
private ToggleSwitch mAudioFilterEnable;
private TextFormatter<Integer> mTalkgroupTextFormatter;
private IntegerFormatter mSquelchTextFormatter = new IntegerFormatter((int)DbPowerMeter.DEFAULT_MINIMUM_POWER,
private final IntegerFormatter mSquelchTextFormatter = new IntegerFormatter((int)DbPowerMeter.DEFAULT_MINIMUM_POWER,
(int)DbPowerMeter.DEFAULT_MAXIMUM_POWER);
private ToggleSwitch mBasebandRecordSwitch;
private SegmentedButton mBandwidthButton;
@@ -84,9 +84,9 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
private SourceConfigurationEditor mSourceConfigurationEditor;
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
private IntegerFormatter mDecimalFormatter = new IntegerFormatter(1, 65535);
private HexFormatter mHexFormatter = new HexFormatter(1, 65535);
private final TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
private final IntegerFormatter mDecimalFormatter = new IntegerFormatter(1, 65535);
private final HexFormatter mHexFormatter = new HexFormatter(1, 65535);
/**
* Constructs an instance
@@ -162,12 +162,15 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
Label talkgroupLabel = new Label("Talkgroup To Assign");
GridPane.setHalignment(talkgroupLabel, HPos.RIGHT);
GridPane.setConstraints(talkgroupLabel, 6, 0);
GridPane.setConstraints(talkgroupLabel, 0, 1);
gridPane.getChildren().add(talkgroupLabel);
GridPane.setConstraints(getTalkgroupField(), 7, 0);
GridPane.setConstraints(getTalkgroupField(), 1, 1);
gridPane.getChildren().add(getTalkgroupField());
GridPane.setConstraints(getAudioFilterEnable(), 2, 1);
gridPane.getChildren().add(getAudioFilterEnable());
mDecoderPane.setContent(gridPane);
//Special handling - the pill button doesn't like to set a selected state if the pane is not expanded,
@@ -275,6 +278,22 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
return mAuxDecoderConfigurationEditor;
}
/**
* Toggle switch for enable/disable the audio filtering in the audio module.
* @return toggle switch.
*/
private ToggleSwitch getAudioFilterEnable()
{
if(mAudioFilterEnable == null)
{
mAudioFilterEnable = new ToggleSwitch("High-Pass Audio Filter");
mAudioFilterEnable.setTooltip(new Tooltip("High-pass filter to remove DC offset and sub-audible signalling"));
mAudioFilterEnable.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAudioFilterEnable;
}
private SegmentedButton getBandwidthButton()
{
if(mBandwidthButton == null)
@@ -298,31 +317,27 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
//decode configuration and we're unable to correctly set the bandwidth setting. As a work
//around, we'll listen for the toggles to be added and update them here. This normally only
//happens when we first instantiate the editor and load an item for editing the first time.
mBandwidthButton.getToggleGroup().getToggles().addListener(new ListChangeListener<Toggle>()
mBandwidthButton.getToggleGroup().getToggles().addListener((ListChangeListener<Toggle>)c ->
{
@Override
public void onChanged(Change<? extends Toggle> c)
//This change event happens when the toggles are added -- we don't need to inspect the change event
if(getItem() != null && getItem().getDecodeConfiguration() instanceof DecodeConfigNBFM)
{
//This change event happens when the toggles are added -- we don't need to inspect the change event
if(getItem() != null && getItem().getDecodeConfiguration() instanceof DecodeConfigNBFM)
//Capture current modified state so that we can reapply after adjusting control states
boolean modified = modifiedProperty().get();
DecodeConfigNBFM config = (DecodeConfigNBFM)getItem().getDecodeConfiguration();
DecodeConfigNBFM.Bandwidth bandwidth = config.getBandwidth();
if(bandwidth == null)
{
//Capture current modified state so that we can reapply after adjusting control states
boolean modified = modifiedProperty().get();
DecodeConfigNBFM config = (DecodeConfigNBFM)getItem().getDecodeConfiguration();
DecodeConfigNBFM.Bandwidth bandwidth = config.getBandwidth();
if(bandwidth == null)
{
bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
}
for(Toggle toggle: getBandwidthButton().getToggleGroup().getToggles())
{
toggle.setSelected(toggle.getUserData() == bandwidth);
}
modifiedProperty().set(modified);
bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
}
for(Toggle toggle: getBandwidthButton().getToggleGroup().getToggles())
{
toggle.setSelected(toggle.getUserData() == bandwidth);
}
modifiedProperty().set(modified);
}
});
}
@@ -449,6 +464,8 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
mSquelchTextFormatter.setValue(decodeConfigNBFM.getSquelchThreshold());
getSquelchAutoTrackSwitch().setDisable(false);
getSquelchAutoTrackSwitch().setSelected(decodeConfigNBFM.isSquelchAutoTrack());
getAudioFilterEnable().setDisable(false);
getAudioFilterEnable().setSelected(decodeConfigNBFM.isAudioFilter());
}
else
{
@@ -464,6 +481,8 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
getSquelchThresholdField().setDisable(true);
getSquelchAutoTrackSwitch().setDisable(true);
getSquelchAutoTrackSwitch().setSelected(false);
getAudioFilterEnable().setDisable(true);
getAudioFilterEnable().setSelected(false);
}
}
@@ -500,6 +519,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
config.setTalkgroup(talkgroup);
config.setSquelchThreshold(mSquelchTextFormatter.getValue());
config.setSquelchAutoTrack(getSquelchAutoTrackSwitch().isSelected());
config.setAudioFilter(getAudioFilterEnable().isSelected());
getItem().setDecodeConfiguration(config);
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-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
@@ -41,13 +41,12 @@ import io.github.dsheirer.rrapi.type.Tag;
import io.github.dsheirer.rrapi.type.Talkgroup;
import io.github.dsheirer.rrapi.type.Type;
import io.github.dsheirer.rrapi.type.Voice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class for decoding type, flavor and voice for systems and formatting talkgroups according to user preferences.
@@ -374,16 +373,10 @@ public class RadioReferenceDecoder
}
}
int lsn = (lcn - 1) * 2 + 1;
TimeslotFrequency timeslot1Frequency = new TimeslotFrequency();
timeslot1Frequency.setNumber(lsn);
timeslot1Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
frequencies.add(timeslot1Frequency);
TimeslotFrequency timeslot2Frequency = new TimeslotFrequency();
timeslot2Frequency.setNumber(lsn + 1);
timeslot2Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
frequencies.add(timeslot2Frequency);
TimeslotFrequency timeslotFrequency = new TimeslotFrequency();
timeslotFrequency.setNumber(lcn);
timeslotFrequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
frequencies.add(timeslotFrequency);
}
return frequencies;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,21 +20,16 @@
package io.github.dsheirer.gui.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster;
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.controlsfx.control.ToggleSwitch;
/**
* RdioScanner calls API configuration editor
@@ -42,6 +37,7 @@ import org.controlsfx.control.ToggleSwitch;
public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerEditor.class);
private static final String API_PATH = "/api/call-upload";
private IntegerTextField mSystemIdTextField;
private IntegerTextField mMaxAgeTextField;
private TextField mApiKeyTextField;
@@ -71,7 +67,14 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
{
getSystemIdTextField().set(item.getSystemID());
getApiKeyTextField().setText(item.getApiKey());
getHostTextField().setText(item.getHost());
String url = item.getHost();
if(url != null)
{
url = url.replace(API_PATH, "");
}
getHostTextField().setText(url);
getMaxAgeTextField().set((int)(item.getMaximumRecordingAge() / 1000));
}
else
@@ -97,7 +100,13 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
{
int systemID = getSystemIdTextField().get() != null ? getSystemIdTextField().get() : 0;
getItem().setSystemID(systemID);
getItem().setHost(getHostTextField().getText());
String host = getHostTextField().getText();
if(host != null)
{
host = host.replace(API_PATH, "");
host += API_PATH;
getItem().setHost(host);
}
getItem().setApiKey(getApiKeyTextField().getText());
getItem().setMaximumRecordingAge(getMaxAgeTextField().get() * 1000);
}
@@ -169,6 +178,11 @@ public class RdioScannerEditor extends AbstractBroadcastEditor<RdioScannerConfig
GridPane.setConstraints(getHostTextField(), 1, row);
mEditorPane.getChildren().add(getHostTextField());
Label apiPath = new Label("/api/call-upload");
GridPane.setHalignment(apiPath, HPos.LEFT);
GridPane.setConstraints(apiPath, 2, row);
mEditorPane.getChildren().add(apiPath);
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
GridPane.setConstraints(maxAgeLabel, 0, ++row);

View File

@@ -32,6 +32,7 @@ public enum Form
CHANNEL_DESCRIPTOR,
CHANNEL_NAME,
CHANNEL_FREQUENCY,
DCS,
DECODER_TYPE,
DTMF,
ENCRYPTION_KEY,

View File

@@ -0,0 +1,48 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.identifier.dcs;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.module.decode.dcs.DCSCode;
import io.github.dsheirer.protocol.Protocol;
/**
* Digital Coded Squelch code identifier. This is decoded from the transmitted signal and represents the FROM role.
*/
public class DCSIdentifier extends Identifier<DCSCode>
{
/**
* Constructs an instance.
* @param code detected.
*/
public DCSIdentifier(DCSCode code)
{
super(code, IdentifierClass.USER, Form.TONE, Role.FROM);
}
@Override
public Protocol getProtocol()
{
return Protocol.DCS;
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,7 +21,6 @@ package io.github.dsheirer.module;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.List;
@@ -50,7 +49,7 @@ public abstract class HistoryModule<T> extends Module implements Listener<T>
*/
public List<T> getItems()
{
return new ArrayList<>(mItems);
return new ArrayList<>(mItems);
}
@Override

View File

@@ -117,8 +117,8 @@ public class ProcessingChain implements Listener<ChannelEvent>
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
private AtomicBoolean mRunning = new AtomicBoolean();
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
private MessageHistory mMessageHistory = new MessageHistory(500);
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(200);
private MessageHistory mMessageHistory = new MessageHistory(200);
private AbstractChannelState mChannelState;
private EventBus mEventBus;
protected Source mSource;

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,11 +23,14 @@ import io.github.dsheirer.message.IMessageProvider;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.sample.Listener;
/**
* Base decoder class.
*/
public abstract class Decoder extends Module implements IMessageProvider
{
/* This has to be a broadcaster in order for references to persist */
private Listener<IMessage> mMessageDistributor = new MessageDistributor();
protected Listener<IMessage> mMessageListener;
private boolean mRunning;
/**
* Decoder - parent class for all decoders, demodulators and components.
@@ -36,14 +39,29 @@ public abstract class Decoder extends Module implements IMessageProvider
{
}
/**
* Starts this decoder and sets the running flag to true.
*/
public void start()
{
//no-op
mRunning = true;
}
/**
* Stops this decoder and sets the running flag to false.
*/
public void stop()
{
//no-op
mRunning = false;
}
/**
* Indicates if this decoder is currently started and in a running state.
* @return true if running or false if not.
*/
public boolean isRunning()
{
return mRunning;
}
public void reset()

View File

@@ -39,11 +39,15 @@ import io.github.dsheirer.module.decode.am.AMDecoderState;
import io.github.dsheirer.module.decode.am.DecodeConfigAM;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.dcs.DCSDecoder;
import io.github.dsheirer.module.decode.dcs.DCSDecoderState;
import io.github.dsheirer.module.decode.dcs.DCSMessageFilter;
import io.github.dsheirer.module.decode.dmr.DMRDecoder;
import io.github.dsheirer.module.decode.dmr.DMRDecoderState;
import io.github.dsheirer.module.decode.dmr.DMRTrafficChannelManager;
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
import io.github.dsheirer.module.decode.dmr.audio.DMRAudioModule;
import io.github.dsheirer.module.decode.dmr.message.filter.DmrMessageFilterSet;
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2Decoder;
import io.github.dsheirer.module.decode.fleetsync2.Fleetsync2DecoderState;
import io.github.dsheirer.module.decode.fleetsync2.FleetsyncMessageFilter;
@@ -77,16 +81,18 @@ import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderC4FM;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderLSM;
import io.github.dsheirer.module.decode.p25.phase1.P25P1DecoderState;
import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25MessageFilterSet;
import io.github.dsheirer.module.decode.p25.phase1.message.filter.P25P1MessageFilterSet;
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderHDQPSK;
import io.github.dsheirer.module.decode.p25.phase2.P25P2DecoderState;
import io.github.dsheirer.module.decode.p25.phase2.message.filter.P25P2MessageFilterSet;
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
import io.github.dsheirer.module.decode.passport.PassportDecoder;
import io.github.dsheirer.module.decode.passport.PassportDecoderState;
import io.github.dsheirer.module.decode.passport.PassportMessageFilter;
import io.github.dsheirer.module.decode.tait.Tait1200Decoder;
import io.github.dsheirer.module.decode.tait.Tait1200DecoderState;
import io.github.dsheirer.module.decode.tait.Tait1200MessageFilter;
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
import io.github.dsheirer.module.demodulate.fm.FMDemodulatorModule;
import io.github.dsheirer.preference.UserPreferences;
@@ -98,12 +104,14 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for creating decoder modules to use in a processing chain for any supported protocols.
*/
public class DecoderFactory
{
private final static Logger mLog = LoggerFactory.getLogger(DecoderFactory.class);
private static final double FM_CHANNEL_BANDWIDTH = 12500.0;
private static final double DEMODULATED_AUDIO_SAMPLE_RATE = 8000.0;
private static final boolean AUDIO_FILTER_ENABLE = true;
/**
* Returns a list of one primary decoder and any auxiliary decoders, as
@@ -179,6 +187,13 @@ public class DecoderFactory
return modules;
}
/**
* Creates decoder modules for APCO-25 Phase 2 decoder
* @param channel configuration
* @param userPreferences reference
* @param modules collection to add to
* @param aliasList for the channel
*/
private static void processP25Phase2(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList) {
modules.add(new P25P2DecoderHDQPSK((DecodeConfigP25Phase2)channel.getDecodeConfiguration()));
@@ -188,6 +203,13 @@ public class DecoderFactory
modules.add(new P25P2AudioModule(userPreferences, 1, aliasList));
}
/**
* Creates decoder modules for APCO-25 Phase 1 decoder
* @param channel configuration
* @param userPreferences reference
* @param modules collection to add to
* @param aliasList for the channel
*/
private static void processP25Phase1(Channel channel, UserPreferences userPreferences, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigP25Phase1 decodeConfig) {
DecodeConfigP25Phase1 p25Config = decodeConfig;
@@ -228,16 +250,32 @@ public class DecoderFactory
}
}
/**
* Creates decoder modules for Passport decoder
* @param channel configuration
* @param modules collection to add to
* @param aliasList for the channel
* @param decodeConfig for the channel
*/
private static void processPassport(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig) {
modules.add(new PassportDecoder(decodeConfig));
modules.add(new PassportDecoderState());
modules.add(new AudioModule(aliasList));
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH));
}
}
/**
* Creates decoder modules for MPT-1327 decoder
* @param channelMapModel to use in calculating traffic channel frequencies
* @param channel configuration
* @param modules collection to add to
* @param aliasList for the channel
* @param channelType for control or traffic
* @param decodeConfig configuration
*/
private static void processMPT1327(ChannelMapModel channelMapModel, Channel channel, List<Module> modules, AliasList aliasList, ChannelType channelType, DecodeConfigMPT1327 decodeConfig) {
DecodeConfigMPT1327 mptConfig = decodeConfig;
ChannelMap channelMap = channelMapModel.getChannelMap(mptConfig.getChannelMapName());
@@ -250,12 +288,12 @@ public class DecoderFactory
// not create a new segment if the processing chain finishes a bit after
// actual call timeout.
long maxAudioSegmentLengthMillis = (callTimeoutMilliseconds + 5000);
modules.add(new AudioModule(aliasList, AbstractAudioModule.DEFAULT_TIMESLOT, maxAudioSegmentLengthMillis));
modules.add(new AudioModule(aliasList, AbstractAudioModule.DEFAULT_TIMESLOT, maxAudioSegmentLengthMillis, AUDIO_FILTER_ENABLE));
SourceType sourceType = channel.getSourceConfiguration().getSourceType();
if(sourceType == SourceType.TUNER || sourceType == SourceType.TUNER_MULTIPLE_FREQUENCIES)
{
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH));
}
if(channelType == ChannelType.STANDARD)
@@ -280,27 +318,48 @@ public class DecoderFactory
}
}
/**
* Creates decoder modules for LTR-Net decoder
* @param channel configuration
* @param modules collection to add to
* @param aliasList for the channel
* @param decodeConfig for the channel
*/
private static void processLTRNet(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfigLTRNet decodeConfig) {
modules.add(new LTRNetDecoder(decodeConfig));
modules.add(new LTRNetDecoderState());
modules.add(new AudioModule(aliasList));
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH));
}
}
/**
* Creates decoder modules for LTR decoder
* @param channel configuration
* @param modules collection to add to
* @param aliasList for the channel
* @param decodeConfig for the channel
*/
private static void processLTRStandard(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfigLTRStandard decodeConfig) {
MessageDirection direction = decodeConfig.getMessageDirection();
modules.add(new LTRStandardDecoder(null, direction));
modules.add(new LTRStandardDecoder(direction));
modules.add(new LTRStandardDecoderState());
modules.add(new AudioModule(aliasList));
modules.add(new AudioModule(aliasList, AUDIO_FILTER_ENABLE));
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
{
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH, DEMODULATED_AUDIO_SAMPLE_RATE));
modules.add(new FMDemodulatorModule(FM_CHANNEL_BANDWIDTH));
}
}
/**
* Creates decoder modules for Narrow Band FM decoder
* @param channel configuration
* @param modules collection to add to
* @param aliasList for the channel
* @param decodeConfig for the channel
*/
private static void processNBFM(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig)
{
if(!(decodeConfig instanceof DecodeConfigNBFM))
@@ -312,9 +371,16 @@ public class DecoderFactory
DecodeConfigNBFM decodeConfigNBFM = (DecodeConfigNBFM)decodeConfig;
modules.add(new NBFMDecoder(decodeConfigNBFM));
modules.add(new NBFMDecoderState(channel.getName(), decodeConfigNBFM));
modules.add(new AudioModule(aliasList));
modules.add(new AudioModule(aliasList, 0, 60000, decodeConfigNBFM.isAudioFilter()));
}
/**
* Creates decoder modules for AM decoder
* @param channel configuration
* @param modules collection to add to
* @param aliasList for the channel
* @param decodeConfig for the channel
*/
private static void processAM(Channel channel, List<Module> modules, AliasList aliasList, DecodeConfiguration decodeConfig) {
if(!(decodeConfig instanceof DecodeConfigAM))
@@ -326,7 +392,7 @@ public class DecoderFactory
DecodeConfigAM decodeConfigAM = (DecodeConfigAM) decodeConfig;
modules.add(new AMDecoder(decodeConfigAM));
modules.add(new AMDecoderState(channel.getName(), decodeConfigAM));
modules.add(new AudioModule(aliasList));
modules.add(new AudioModule(aliasList, 0, 60000, AUDIO_FILTER_ENABLE));
}
/**
@@ -381,7 +447,7 @@ public class DecoderFactory
}
/**
* Constructs a list of auxiliary decoders, as specified in the configuration
* Constructs a list of auxiliary decoders, as specified in the channel configuration
*
* @param config - auxiliary configuration
* @return - list of auxiliary decoders
@@ -396,6 +462,10 @@ public class DecoderFactory
{
switch(auxDecoder)
{
case DCS:
modules.add(new DCSDecoder());
modules.add(new DCSDecoderState());
break;
case FLEETSYNC2:
modules.add(new Fleetsync2Decoder());
modules.add(new Fleetsync2DecoderState());
@@ -427,7 +497,7 @@ public class DecoderFactory
*/
public static FilterSet<IMessage> getMessageFilters(List<Module> modules)
{
FilterSet<IMessage> filterSet = new FilterSet<>();
FilterSet<IMessage> filterSet = new FilterSet<>("Message Filters");
for(Module module : modules)
{
@@ -437,25 +507,28 @@ public class DecoderFactory
}
}
/* If we don't have any filters, add an ALL-PASS filter */
if(filterSet.getFilters().isEmpty())
{
filterSet.addFilter(new AllPassFilter<>());
}
//Add an all-others filter as a catch-all for anything that isn't handled by the decoder filters.
filterSet.addFilter(new AllPassFilter<>("All Other Messages Filter"));
return filterSet;
}
/**
* Returns a set of IMessageFilter objects (FilterSets or Filters) that
* can process all of the messages produced by the specified decoder type.
* can process all messages produced by the specified decoder type.
*/
public static List<IFilter<IMessage>> getMessageFilter(DecoderType decoder)
{
ArrayList<IFilter<IMessage>> filters = new ArrayList<>();
List<IFilter<IMessage>> filters = new ArrayList<>();
switch(decoder)
{
case DCS:
filters.add(new DCSMessageFilter());
break;
case DMR:
filters.add(new DmrMessageFilterSet());
break;
case FLEETSYNC2:
filters.add(new FleetsyncMessageFilter());
break;
@@ -475,13 +548,16 @@ public class DecoderFactory
filters.add(new MPT1327MessageFilter());
break;
case P25_PHASE1:
filters.add(new P25MessageFilterSet());
filters.add(new P25P1MessageFilterSet());
break;
case P25_PHASE2:
filters.add(new P25P2MessageFilterSet());
break;
case PASSPORT:
filters.add(new PassportMessageFilter());
break;
case DMR:
//filters.add(new DMR) //todo: not finished
case TAIT_1200:
filters.add(new Tait1200MessageFilter());
break;
default:
break;
@@ -518,7 +594,7 @@ public class DecoderFactory
case P25_PHASE2:
return new DecodeConfigP25Phase2();
default:
throw new IllegalArgumentException("DecodeConfigFactory - unknown decoder type [" + decoder.toString() + "]");
throw new IllegalArgumentException("DecodeConfigFactory - unknown decoder type [" + decoder + "]");
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,7 +19,6 @@
package io.github.dsheirer.module.decode;
import io.github.dsheirer.protocol.Protocol;
import java.util.EnumSet;
/**
@@ -39,6 +38,7 @@ public enum DecoderType
P25_PHASE2("P25 Phase 2", "P25-2", Protocol.APCO25_PHASE2),
//Auxiliary Decoders
DCS("Digital Coded Squelch (DCS)", "DCS", Protocol.DCS),
FLEETSYNC2("Fleetsync II", "Fleetsync2", Protocol.FLEETSYNC),
LJ_1200("LJ1200 173.075", "LJ1200", Protocol.LOJACK),
MDC1200("MDC1200", "MDC1200", Protocol.MDC1200),
@@ -73,7 +73,8 @@ public enum DecoderType
* Auxiliary decoders that operate on in-band signalling in the decoded audio channel
*/
public static final EnumSet<DecoderType> AUX_DECODERS =
EnumSet.of(DecoderType.FLEETSYNC2,
EnumSet.of(DecoderType.DCS,
DecoderType.FLEETSYNC2,
DecoderType.LJ_1200,
DecoderType.MDC1200,
DecoderType.TAIT_1200);

View File

@@ -26,6 +26,7 @@ import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.p25.identifier.channel.StandardChannel;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.ISourceEventListener;
@@ -100,9 +101,8 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
if(mDecodeEvent == null)
{
mDecodeEvent = DecodeEvent.builder(System.currentTimeMillis())
mDecodeEvent = DecodeEvent.builder(DecodeEventType.CALL, System.currentTimeMillis())
.channel(mChannelDescriptor)
.eventDescription("CALL")
.details(getDecoderType().name())
.identifiers(new IdentifierCollection(getIdentifierCollection().getIdentifiers()))
.build();
@@ -121,6 +121,7 @@ public abstract class AnalogDecoderState extends DecoderState implements ISource
startCallEvent();
}
getIdentifierCollection().update(getTalkgroupIdentifier());
mDecodeEvent.update(System.currentTimeMillis());
broadcast(mDecodeEvent);
}

View File

@@ -0,0 +1,286 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dcs;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
/**
* DCS codes as defined in ETSI TS 103 236 V1.1.1
*
* Code values are the bit reversed integer values of the 23-bit binary sequence representation. The values are bit
* reversed for normal (N) codes and as specified in the ETSI chart for the inverted (I) codes to align with the
* transmitted format.
*/
public enum DCSCode
{
//Inverted codes
I023(7747603),
I025(7043093),
I026(6674454),
I031(5371929),
I032(6248474),
I043(5990435),
I047(1038375),
I051(8169513),
I054(7292972),
I065(6101045),
I071(6789177),
I072(6895674),
I073(3041339),
I074(7632956),
I114(3532876),
I115(7518285),
I116(8132686),
I125(505941),
I131(4012121),
I132(3381338),
I134(3070044),
I143(3647587),
I152(2017386),
I155(4511853),
I156(4880494),
I162(7063666),
I165(3266677),
I172(391290),
I174(1620092),
I205(7248005),
I223(6875283),
I226(8063126),
I243(4569251),
I244(2074788),
I245(5830821),
I251(6453417),
I261(1538225),
I263(6195379),
I265(4442293),
I271(7948473),
I306(850118),
I311(3725513),
I315(7104717),
I331(2353369),
I343(2717923),
I346(3840230),
I351(964841),
I364(6838516),
I365(3082485),
I371(1411321),
I411(7825673),
I412(7981322),
I413(4102411),
I423(4954387),
I431(7100697),
I432(6486298),
I445(8096037),
I464(2615604),
I465(6338869),
I466(7215414),
I503(3959107),
I506(3115334),
I516(4307278),
I532(932186),
I546(1698150),
I565(817525),
I606(6134150),
I612(6756746),
I624(1005972),
I627(129431),
I631(7506329),
I632(8137114),
I654(4995500),
I662(2390450),
I664(3750324),
I703(2275779),
I712(776650),
I723(3770835),
I731(1984985),
I732(1108442),
I734(895452),
I743(1366499),
I754(2161132),
//Normal codes
N023(6557239),
N025(5508971),
N026(3411411),
N031(4984773),
N032(2887037),
N043(6425453),
N047(7474680),
N051(4852383),
N054(1706363),
N065(5639261),
N071(5115123),
N072(3018315),
N073(7211834),
N074(1969943),
N114(1641430),
N115(5836455),
N116(3738655),
N125(5574384),
N131(5049950),
N132(2952422),
N134(1904058),
N143(6490870),
N152(2820540),
N155(5967249),
N156(3870505),
N162(2558443),
N165(5705158),
N172(3084240),
N174(2035340),
N205(5278907),
N223(6589323),
N226(3442799),
N243(6459089),
N244(1215228),
N245(5410701),
N251(4886307),
N261(4624244),
N263(6719677),
N265(5671393),
N271(5146959),
N306(3248024),
N311(4820366),
N315(5868315),
N331(5082082),
N343(6524746),
N346(3378350),
N351(4951736),
N364(1543435),
N365(5736570),
N371(5212372),
N411(4737911),
N412(2640335),
N413(6835390),
N423(6573289),
N431(5000475),
N432(2903971),
N445(5392623),
N464(1461234),
N465(5656195),
N466(3558459),
N503(6376222),
N506(3229946),
N516(3755713),
N532(2969144),
N546(3361740),
N565(5721880),
N606(3198173),
N612(2673779),
N624(1363320),
N627(7655360),
N631(5032103),
N632(2935327),
N654(1756697),
N662(2543378),
N664(1494606),
N703(6409890),
N712(2739688),
N723(6670542),
N731(5097788),
N732(3001220),
N734(1952472),
N743(6540692),
N754(1822594),
UNKNOWN(0);
private int mValue;
DCSCode(int value)
{
mValue = value;
}
/**
* Inverted DCS codes
*/
public static final EnumSet<DCSCode> INVERTED_CODES = EnumSet.range(I023, I754);
/**
* Standard DCS codes
*/
public static final EnumSet<DCSCode> STANDARD_CODES = EnumSet.range(N023,N754);
/**
* Lookup map for quickly finding a DCS code from the transmitted value.
*/
private static final Map<Integer,DCSCode> CODE_MAP = new HashMap<>();
static
{
for(DCSCode code: EnumSet.range(I023, N754))
{
CODE_MAP.put(code.getValue(), code);
}
}
/**
* Integer value of the code from transmitted bits.
* @return value
*/
public int getValue()
{
return mValue;
}
/**
* Indicates if this is an inverted DCS code (true) or a normal DCS code (false).
* @return true if inverted.
*/
public boolean isInverted()
{
return INVERTED_CODES.contains(this);
}
@Override
public String toString()
{
return "DCS-" + name().substring(1, 4) + (isInverted() ? " Inverted" : "");
}
/**
* Indicates if the value maps to a known DCS code.
* @param value to test
* @return true if the value matches a DCS code.
*/
public static boolean hasValue(int value)
{
return CODE_MAP.containsKey(value);
}
/**
* Get the DCS code that matches the value, otherwise return UNKNOWN.
* @param value to lookup
* @return matching DCS code or UNKNOWN.
*/
public static DCSCode fromValue(int value)
{
DCSCode code = CODE_MAP.get(value);
if(code == null)
{
code = UNKNOWN;
}
return code;
}
}

View File

@@ -0,0 +1,293 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dcs;
import io.github.dsheirer.dsp.filter.FilterFactory;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.module.decode.Decoder;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.IRealBufferListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Digital Coded Squelch (DCS) demodulator/decoder designed to work with FM demodulated 8 kHz input audio samples and
* decode 134.4-baud signalling present in the unfiltered demodulated audio stream.
*
* This decoder monitors the incoming sample stream looking for either a positive or negative sequence of samples where
* the slope of these values exceeds a threshold and causes the symbol state to flip. The symbol state remains across
* successive baud periods until the next opposing slope state change sequence is detected.
*
* This decoder uses a slope detector to decode the 134.4 bits per second stream and identify the DCS code. It is
* designed to work with 8 kHz audio sample rate. Each baud period is (8,000 / 134.4) = 59.52 samples per symbol and
* a full DCS tone sequence is 23 bits * 59.52 samples/bit = 1369 samples. The DCS tone sequence is continuously
* transmitted at a rate of 134.4 bits-per-second / 23 = 5.84 repeats per second while the source continues transmitting.
*/
public class DCSDecoder extends Decoder implements IRealBufferListener, Listener<float[]>
{
private static final Logger mLog = LoggerFactory.getLogger(DCSDecoder.class);
//Slope calculation sumXX value is always the same for 59 evenly spaced samples
private static final double SLOPE_CALCULATION_SUM_XX = 2247.5;
private static final float BAUD_LENGTH = 8000.0f / 134.4f;
private static final float SLOPE_THRESHOLD = 0.002750f;
private static final int MAX_ONES_SEQUENCE = 6;
private static final int OVERLAP = (int)Math.ceil(BAUD_LENGTH);
private static final int POST_TRANSITION_SAMPLES_TO_SKIP = 30;
private static final int SLOPE_CALCULATION_PERIOD = 30;
private static final int IDEAL_SYMBOL_TRANSITION_MIN = 11;
private static final int IDEAL_SYMBOL_TRANSITION_MAX = 19;
private static final int CODE_MASK = 0x7FFFFF; //23-bit mask
private static float[] sLowPassFilterCoefficients;
private boolean mSymbol = false;
private float mBaudCounter = 0f;
private float mMaxSlope = 0;
private float[] mResidual = new float[OVERLAP];
private int mCode = 0;
private int mExcessiveOneSequenceCounter = 0;
static
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(8000)
.gridDensity(16)
.oddLength(true)
.passBandCutoff(200)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(300)
.stopBandAmplitude(0.0)
.stopBandRipple(0.03) //Approximately 60 dB attenuation
.build();
try
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
sLowPassFilterCoefficients = designer.getImpulseResponse();
}
}
catch(FilterDesignException fde)
{
mLog.error("Filter design error", fde);
}
}
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
/**
* Constructs an instance
*/
public DCSDecoder()
{
}
/**
* Decoder type
*/
@Override
public DecoderType getDecoderType()
{
return DecoderType.DCS;
}
/**
* Implementation of the IRealBufferListener interface
*/
@Override
public Listener<float[]> getBufferListener()
{
return this::receive;
}
/**
* Processes the demodulated 8 kHz audio samples to extract the 300-baud LTR signalling and delivers the decoded
* symbol array to the registered listener.
* @param samples to demodulate.
*/
public void receive(float[] samples)
{
if(getMessageListener() != null)
{
float[] filtered = mLowPassFilter.filter(samples);
float[] buffer = new float[filtered.length + OVERLAP];
int samplesToSkip = 0;
int timingAdjust = 0;
float slope = 0;
try
{
System.arraycopy(mResidual, 0, buffer, 0, mResidual.length);
System.arraycopy(filtered, 0, buffer, mResidual.length, filtered.length);
System.arraycopy(filtered, filtered.length - OVERLAP, mResidual, 0, OVERLAP);
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
{
//Don't calculate slope if we're within the baud period following the last symbol transition
if(samplesToSkip > 0)
{
samplesToSkip--;
}
else
{
slope = calculateSlope(buffer, bufferPointer);
if(mSymbol)
{
//Look for crest of slope at lowest point below the negative slope threshold for transition
if(slope > mMaxSlope && mMaxSlope < -SLOPE_THRESHOLD)
{
mSymbol = false;
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 19 samples
if(mBaudCounter < IDEAL_SYMBOL_TRANSITION_MIN)
{
timingAdjust = 1;
}
else if(mBaudCounter > IDEAL_SYMBOL_TRANSITION_MAX)
{
timingAdjust = -1;
}
else
{
timingAdjust = 0;
}
mBaudCounter += timingAdjust;
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
}
else if(slope < mMaxSlope)
{
mMaxSlope = slope;
}
}
else
{
//Loop for crest of slope at the highest point above the positive threshold
if(slope < mMaxSlope && mMaxSlope > SLOPE_THRESHOLD)
{
mSymbol = true;
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 19 samples
if(mBaudCounter < IDEAL_SYMBOL_TRANSITION_MIN)
{
timingAdjust = 1;
}
else if(mBaudCounter > IDEAL_SYMBOL_TRANSITION_MAX)
{
timingAdjust = -1;
}
else
{
timingAdjust = 0;
}
mBaudCounter += timingAdjust;
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
}
else if(slope > mMaxSlope)
{
mMaxSlope = slope;
}
}
}
mBaudCounter++;
//Update the current code value once we've processed a baud/symbol period of samples
if(mBaudCounter > BAUD_LENGTH)
{
//Update the code value adding in the current symbol
mCode = (Integer.rotateLeft(mCode, 1) + (mSymbol ? 1 : 0)) & CODE_MASK;
if(DCSCode.hasValue(mCode))
{
getMessageListener().receive(new DCSMessage(DCSCode.fromValue(mCode), System.currentTimeMillis()));
}
mBaudCounter -= BAUD_LENGTH;
//Check for a continuous sequence of ones that exceeds the max (6) for any DCS code and
//flip/revert the symbol state back to zero.
if(mSymbol)
{
mExcessiveOneSequenceCounter++;
if(mExcessiveOneSequenceCounter > MAX_ONES_SEQUENCE)
{
mSymbol = false;
mMaxSlope = -1f;
mExcessiveOneSequenceCounter = 0;
}
}
else
{
mExcessiveOneSequenceCounter = 0;
}
}
}
}
catch(Exception e)
{
mLog.warn("Unexpected error while processing DCS samples", e);
}
}
}
/**
* Calculates the slope of the 30 samples in the values array starting at the specified offset. The baud period
* is slightly more than 59 samples, however the voltage state transition occurs over a 30-sample period.
*
* Note: code modified from Apache Commons Math library, SimpleRegression class.
* @param samples to load from
* @param offset to the sample start for the slope calculation
*/
public float calculateSlope(float[] samples, int offset)
{
/** mean of accumulated x & y values, used in updating formulas */
double xbar = 0;
double ybar = samples[offset];
// double sumXX = 0;
double sumXY = 0;
double fact1, dx, dy;
for(int x = 1; x < SLOPE_CALCULATION_PERIOD; x++)
{
fact1 = 1.0 + x;
// final double fact2 = x / (1.0 + x);
dx = x - xbar;
dy = samples[offset + x] - ybar;
// sumXX += dx * dx * fact2;
sumXY += dx * dy * (x / (1.0 + x));
xbar += dx / fact1;
ybar += dy / fact1;
}
//Transfer the sumXX value to SLOPE_CALCULATION_SUM_XX since it's a constant value
// mLog.info("SumXX: " + sumXX);
return (float)(sumXY / SLOPE_CALCULATION_SUM_XX);
}
}

View File

@@ -0,0 +1,123 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dcs;
import io.github.dsheirer.channel.state.DecoderState;
import io.github.dsheirer.channel.state.DecoderStateEvent;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.decode.DecoderType;
import java.util.HashMap;
import java.util.Map;
/**
* Maintains the decoder state from the stream of decoded DCS code messages.
*/
public class DCSDecoderState extends DecoderState
{
private Map<DCSCode,Integer> mDcsCodeCountsMap = new HashMap<>();
private DCSCode mCurrentCode = DCSCode.UNKNOWN;
/**
* Constructs an instance
*/
public DCSDecoderState()
{
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.DCS;
}
@Override
public void receive(IMessage message)
{
if(message instanceof DCSMessage dcsMessage)
{
mCurrentCode = dcsMessage.getDCSCode();
if(!mDcsCodeCountsMap.containsKey(mCurrentCode))
{
mDcsCodeCountsMap.put(mCurrentCode, 1);
}
else
{
mDcsCodeCountsMap.put(mCurrentCode, mDcsCodeCountsMap.get(mCurrentCode) + 1);
}
getIdentifierCollection().update(dcsMessage.getIdentifiers());
}
}
@Override
public void receiveDecoderStateEvent(DecoderStateEvent event)
{
switch(event.getEvent())
{
case REQUEST_RESET:
resetState();
break;
default:
break;
}
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append("=============================\n");
sb.append("Decoder:\tDigital Coded Squelch (DCS)\n\n");
if(mDcsCodeCountsMap.isEmpty())
{
sb.append(" Detected Codes: (none)\n");
}
else
{
for(Map.Entry<DCSCode,Integer> entry: mDcsCodeCountsMap.entrySet())
{
sb.append(" ").append(entry.getKey()).append(" - Count: ").append(entry.getValue());
if(entry.getKey().equals(mCurrentCode))
{
sb.append(" - Current\n");
}
else
{
sb.append("\n");
}
}
}
return sb.toString();
}
@Override
public void init()
{
}
@Override
public void stop()
{
}
}

View File

@@ -0,0 +1,79 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dcs;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
import io.github.dsheirer.message.Message;
import io.github.dsheirer.protocol.Protocol;
import java.util.Collections;
import java.util.List;
/**
* Digital Coded Squelch (DCS) tone detected message
*/
public class DCSMessage extends Message
{
private final DCSCode mDCSCode;
/**
* Constructs an instance
* @param code that was detected
* @param timestamp when the code was detected
*/
public DCSMessage(DCSCode code, long timestamp)
{
super(timestamp);
mDCSCode = code;
}
@Override
public String toString()
{
return "Digital Coded Squelch (DCS) Detected: " + mDCSCode.toString();
}
/**
* The DCS code that was detected.
* @return code
*/
public DCSCode getDCSCode()
{
return mDCSCode;
}
@Override
public boolean isValid()
{
return true; //We only send a message when the tone was valid
}
@Override
public Protocol getProtocol()
{
return Protocol.DCS;
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.singletonList(new DCSIdentifier(mDCSCode));
}
}

View File

@@ -0,0 +1,76 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dcs;
import io.github.dsheirer.filter.Filter;
import io.github.dsheirer.filter.FilterElement;
import io.github.dsheirer.message.IMessage;
import java.util.function.Function;
/**
* Message filter for Digital Coded Squelch (DCS)
*/
public class DCSMessageFilter extends Filter<IMessage,String>
{
private static final String DCS_KEY = "DCS";
private final KeyExtractor mKeyExtractor = new KeyExtractor();
/**
* Constructor
*/
public DCSMessageFilter()
{
super("DCS Messages");
add(new FilterElement(DCS_KEY));
}
/**
* Indicates that this filter can process DCS messages.
* @param message to test
* @return true if the message can be processed
*/
@Override
public boolean canProcess(IMessage message)
{
return message instanceof DCSMessage && super.canProcess(message);
}
/**
* Key extractor that always returns the same key constant.
* @return extractor function.
*/
@Override
public Function<IMessage, String> getKeyExtractor()
{
return mKeyExtractor;
}
/**
* Key extractor
*/
private class KeyExtractor implements Function<IMessage,String>
{
@Override
public String apply(IMessage message)
{
return DCS_KEY;
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -84,6 +84,20 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
setSampleRate(25000.0);
}
@Override
public void start()
{
super.start();
mQPSKDemodulator.start();
}
@Override
public void stop()
{
super.stop();
mQPSKDemodulator.stop();
}
/**
* DMR decoder type.
*/
@@ -318,22 +332,4 @@ public class DMRDecoder extends FeedbackDecoder implements ISourceEventListener,
{
return DMRDecoder.this;
}
/**
* Starts the decoder
*/
@Override
public void start()
{
//No-op
}
/**
* Stops the decoder
*/
@Override
public void stop()
{
//No-op
}
}

View File

@@ -57,6 +57,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcem
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.VoteNowAdvice;
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.grant.ChannelGrant;
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
@@ -65,25 +66,33 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoice
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusEncryptedVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EmbeddedParameters;
import io.github.dsheirer.module.decode.dmr.message.voice.embedded.EncryptionParameters;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
import io.github.dsheirer.module.decode.ip.hytera.rrs.HyteraRrsPacket;
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
import io.github.dsheirer.module.decode.ip.hytera.shortdata.HyteraShortDataPacket;
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.ars.ARSPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
import io.github.dsheirer.module.decode.ip.mototrbo.xcmp.XCMPPacket;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
import io.github.dsheirer.util.PacketUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -102,7 +111,7 @@ public class DMRDecoderState extends TimeslotDecoderState
private DecodeEvent mCurrentCallEvent;
private long mCurrentFrequency;
private boolean mIgnoreCRCChecksums;
private Map<Integer,DecodeEvent> mDetectedCallEventsMap = new TreeMap<>();
private Map<DMRChannel,DecodeEvent> mDetectedCallEventsMap = new HashMap<>();
private static final AddChannelRotationActiveStateRequest CAPACITY_PLUS_ACTIVE_STATE_REQUEST =
new AddChannelRotationActiveStateRequest(State.ACTIVE);
@@ -286,12 +295,10 @@ public class DMRDecoderState extends TimeslotDecoderState
{
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
DecodeEvent smsEvent = DMRDecodeEvent.builder(sms.getTimestamp())
.eventType(DecodeEventType.SMS)
.eventDescription(DecodeEventType.SMS.toString())
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, sms.getTimestamp())
.details("MESSAGE: " + sms.getSMS())
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
.timeslot(sms.getTimeslot())
.timeslot(getTimeslot())
.build();
broadcast(smsEvent);
}
@@ -312,33 +319,106 @@ public class DMRDecoderState extends TimeslotDecoderState
mic.update(hyteraSmsPacket.getSource());
mic.update(hyteraSmsPacket.getDestination());
DecodeEvent smsEvent = DMRDecodeEvent.builder(packet.getTimestamp())
.eventDescription(DecodeEventType.SMS.name())
DecodeEvent smsEvent = DMRDecodeEvent.builder(DecodeEventType.SMS, packet.getTimestamp())
.identifiers(mic)
.timeslot(packet.getTimeslot())
.timeslot(getTimeslot())
.details("SMS:" + hyteraSmsPacket.getSMS())
.build();
broadcast(smsEvent);
}
//Hytera Radio Registration Service (RRS)
else if(packet.getPacket() instanceof HyteraRrsPacket rrs)
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
StringBuilder sb = new StringBuilder();
sb.append("HYTERA RRS REGISTER RADIO:");
sb.append(rrs.getDestination());
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
.identifiers(mic)
.timeslot(getTimeslot())
.details(sb.toString())
.build();
broadcast(shortDataEvent);
}
//Hytera Short Data
else if(packet.getPacket() instanceof HyteraShortDataPacket hsdp)
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
StringBuilder sb = new StringBuilder();
sb.append("HYTERA");
if(hsdp.getPacketSequence().isEncrypted())
{
HyteraDataEncryptionHeader hdeh = (HyteraDataEncryptionHeader)hsdp.getPacketSequence().getProprietaryDataHeader();
sb.append(" ENCRYPTED ALGORITHM:").append(hdeh.getAlgorithm());
sb.append(" KEY:").append(hdeh.getKeyId());
sb.append(" IV:").append(hdeh.getIV());
}
sb.append(" SHORT DATA:").append(hsdp.getMessage().toHexString());
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
.identifiers(mic)
.timeslot(getTimeslot())
.details(sb.toString())
.build();
broadcast(shortDataEvent);
}
//Unknown Hytera Long Data Service Token Message
else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket)
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(packet.getTimestamp())
.eventDescription(DecodeEventType.UNKNOWN_PACKET.name())
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(DecodeEventType.UNKNOWN_PACKET, packet.getTimestamp())
.identifiers(mic)
.timeslot(packet.getTimeslot())
.details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
.timeslot(getTimeslot())
.details("HYTERA LONG DATA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
.build();
broadcast(unknownTokenEvent);
}
//Motorola ARS
else if(packet.getPacket() instanceof ARSPacket ars)
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.RADIO_REGISTRATION_SERVICE, packet.getTimestamp())
.identifiers(mic)
.timeslot(getTimeslot())
.details(ars.toString())
.build();
broadcast(shortDataEvent);
}
//Motorola LRRP
else if(packet.getPacket() instanceof LRRPPacket lrrp)
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.LRRP, packet.getTimestamp())
.identifiers(mic)
.timeslot(getTimeslot())
.details(lrrp.toString())
.build();
broadcast(shortDataEvent);
}
//Motorola XCMP
else if(packet.getPacket() instanceof XCMPPacket xcmp)
{
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
DecodeEvent shortDataEvent = DMRDecodeEvent.builder(DecodeEventType.XCMP, packet.getTimestamp())
.identifiers(mic)
.timeslot(getTimeslot())
.details(xcmp.toString())
.build();
broadcast(shortDataEvent);
}
else
{
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
.eventDescription(DecodeEventType.DATA_PACKET.name())
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
.timeslot(packet.getTimeslot())
DecodeEvent packetEvent = DMRDecodeEvent.builder(DecodeEventType.DATA_PACKET, packet.getTimestamp())
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
.timeslot(getTimeslot())
.details(packet.toString())
.build();
@@ -347,10 +427,9 @@ public class DMRDecoderState extends TimeslotDecoderState
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
if (geoPosition != null) {
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp())
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(DecodeEventType.GPS, packet.getTimestamp())
.channel(getCurrentChannel())
.eventDescription(DecodeEventType.GPS.toString())
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
.identifiers(new IdentifierCollection(packet.getIdentifiers()))
.protocol(Protocol.LRRP)
.location(geoPosition)
.build();
@@ -376,6 +455,19 @@ public class DMRDecoderState extends TimeslotDecoderState
{
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
}
if(message.getSyncPattern() == DMRSyncPattern.BS_VOICE_FRAME_F && message instanceof VoiceEMBMessage voiceEmb)
{
if(voiceEmb.hasEmbeddedParameters())
{
EmbeddedParameters embedded = voiceEmb.getEmbeddedParameters();
if(embedded.getShortBurst() instanceof EncryptionParameters arc4)
{
updateEncryptedCall(arc4, true, voiceEmb.getTimestamp());
}
}
}
}
/**
@@ -505,14 +597,8 @@ public class DMRDecoderState extends TimeslotDecoderState
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD:
if(csbk instanceof Acknowledge)
{
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(((Acknowledge)csbk).getReason().toString())
.build();
broadcast(ackEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE,
((Acknowledge) csbk).getReason().toString()));
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
break;
@@ -520,14 +606,8 @@ public class DMRDecoderState extends TimeslotDecoderState
case STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC:
if(csbk instanceof Acknowledge)
{
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(((Acknowledge)csbk).getReason().toString())
.build();
broadcast(ackEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE,
((Acknowledge) csbk).getReason().toString()));
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
@@ -537,33 +617,16 @@ public class DMRDecoderState extends TimeslotDecoderState
switch(((Ahoy)csbk).getServiceKind())
{
case AUTHENTICATE_REGISTER_RADIO_CHECK_SERVICE:
DecodeEvent registerEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(DecodeEventType.REGISTER.toString())
.build();
broadcast(registerEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, DecodeEventType.REGISTER.toString()));
break;
case CANCEL_CALL_SERVICE:
DecodeEvent cancelEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("CANCEL CALL")
.build();
broadcast(cancelEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND, "CANCEL CALL"));
break;
case SUPPLEMENTARY_SERVICE:
if(csbk instanceof StunReviveKill)
{
DecodeEvent stunEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(((StunReviveKill)csbk).getCommand() + " RADIO")
.build();
broadcast(stunEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
((StunReviveKill)csbk).getCommand() + " RADIO"));
}
break;
case FULL_DUPLEX_MS_TO_MS_PACKET_CALL_SERVICE:
@@ -577,15 +640,9 @@ public class DMRDecoderState extends TimeslotDecoderState
if(csbk instanceof ServiceRadioCheck)
{
ServiceRadioCheck src = (ServiceRadioCheck)csbk;
DecodeEvent checkEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RADIO_CHECK.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details(src.getServiceDescription() + " SERVICE FOR " +
(src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO"))
.build();
broadcast(checkEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.RADIO_CHECK,
src.getServiceDescription() + " SERVICE FOR " +
(src.isTalkgroupTarget() ? "TALKGROUP" : "RADIO")));
}
break;
}
@@ -599,14 +656,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(aloha.hasRadioIdentifier())
{
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("Aloha Acknowledge")
.build();
broadcast(ackEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge"));
resetState();
}
}
@@ -618,24 +668,13 @@ public class DMRDecoderState extends TimeslotDecoderState
switch(((Announcement)csbk).getAnnouncementType())
{
case MASS_REGISTRATION:
DecodeEvent massEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.REGISTER.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("MASS REGISTRATION")
.build();
broadcast(massEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.REGISTER, "MASS REGISTRATION"));
break;
case VOTE_NOW_ADVICE:
if(csbk instanceof VoteNowAdvice)
{
DecodeEvent voteEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode())
.build();
broadcast(voteEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
"VOTE NOW FOR " + ((VoteNowAdvice)csbk).getVotedSystemIdentityCode()));
}
break;
}
@@ -653,13 +692,8 @@ public class DMRDecoderState extends TimeslotDecoderState
case STANDARD_PROTECT:
if(csbk instanceof Protect)
{
DecodeEvent protectEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.COMMAND.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("PROTECT: " + ((Protect)csbk).getProtectKind())
.build();
broadcast(protectEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.COMMAND,
"PROTECT: " + ((Protect)csbk).getProtectKind()));
}
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CALL, getTimeslot()));
break;
@@ -701,27 +735,22 @@ public class DMRDecoderState extends TimeslotDecoderState
ChannelGrant dataGrant = (ChannelGrant)csbk;
DMRChannel channel = dataGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
csbk.getTimestamp(), csbk.isEncrypted());
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent event = mDetectedCallEventsMap.get(channel);
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.DATA_CALL.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
new IdentifierCollection(csbk.getIdentifiers()));
mDetectedCallEventsMap.put(channel, event);
}
else
{
@@ -740,27 +769,22 @@ public class DMRDecoderState extends TimeslotDecoderState
ChannelGrant tgGrant = (ChannelGrant)csbk;
DMRChannel channel = tgGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent event = mDetectedCallEventsMap.get(channel);
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.CALL_GROUP.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
event = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
new IdentifierCollection(csbk.getIdentifiers()));
mDetectedCallEventsMap.put(channel, event);
}
else
{
@@ -778,27 +802,23 @@ public class DMRDecoderState extends TimeslotDecoderState
{
ChannelGrant channelGrant = (ChannelGrant)csbk;
DMRChannel channel = channelGrant.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers,
csbk.getOpcode(), csbk.getTimestamp(), csbk.isEncrypted());
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent event = mDetectedCallEventsMap.get(channel);
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.CALL_UNIT_TO_UNIT.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
event = getDecodeEvent(csbk, DecodeEventType.CALL_UNIT_TO_UNIT, channel,
new IdentifierCollection(csbk.getIdentifiers()));
mDetectedCallEventsMap.put(channel, event);
}
else
{
@@ -817,15 +837,7 @@ public class DMRDecoderState extends TimeslotDecoderState
if(cmAloha.hasRadioIdentifier())
{
DecodeEvent ackEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.eventDescription(DecodeEventType.RESPONSE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.details("Aloha Acknowledge")
.build();
broadcast(ackEvent);
broadcast(getDecodeEvent(csbk, DecodeEventType.RESPONSE, "Aloha Acknowledge"));
resetState();
}
}
@@ -837,27 +849,22 @@ public class DMRDecoderState extends TimeslotDecoderState
ConnectPlusDataChannelGrant cpdcg = (ConnectPlusDataChannelGrant)csbk;
DMRChannel channel = cpdcg.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
csbk.getTimestamp(), csbk.isEncrypted());
}
else
{
DecodeEvent event = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent event = mDetectedCallEventsMap.get(channel);
if(isStale(event, csbk.getTimestamp(), csbk.getIdentifiers()))
{
event = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.DATA_CALL.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), event);
event = getDecodeEvent(csbk, DecodeEventType.DATA_CALL, channel,
new IdentifierCollection(csbk.getIdentifiers()));
mDetectedCallEventsMap.put(channel, event);
}
else
{
@@ -871,21 +878,19 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
case MOTOROLA_CONPLUS_REGISTRATION_REQUEST:
DecodeEvent event = DMRDecodeEvent.builder(csbk.getTimestamp())
DecodeEvent event = DMRDecodeEvent.builder(DecodeEventType.REQUEST, csbk.getTimestamp())
.details("Registration Request")
.eventDescription(DecodeEventType.REGISTER.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
.timeslot(getTimeslot())
.build();
broadcast(event);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
case MOTOROLA_CONPLUS_REGISTRATION_RESPONSE:
DecodeEvent regRespEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
DecodeEvent regRespEvent = DMRDecodeEvent.builder(DecodeEventType.RESPONSE, csbk.getTimestamp())
.details("Registration Response")
.eventDescription(DecodeEventType.REGISTER.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
.timeslot(getTimeslot())
.build();
broadcast(regRespEvent);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
@@ -896,27 +901,22 @@ public class DMRDecoderState extends TimeslotDecoderState
ConnectPlusVoiceChannelUser cpvcu = (ConnectPlusVoiceChannelUser)csbk;
DMRChannel channel = cpvcu.getChannel();
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
if(hasTrafficChannelManager())
{
IdentifierCollection mergedIdentifiers = getMergedIdentifierCollection(csbk.getIdentifiers());
mTrafficChannelManager.processChannelGrant(channel, mergedIdentifiers, csbk.getOpcode(),
csbk.getTimestamp(), csbk.isEncrypted());
}
else
{
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel.getLogicalSlotNumber());
DecodeEvent detectedEvent = mDetectedCallEventsMap.get(channel);
if(isStale(detectedEvent, csbk.getTimestamp(), csbk.getIdentifiers()))
{
detectedEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.eventDescription(DecodeEventType.CALL_GROUP.toString())
.identifiers(mergedIdentifiers)
.timeslot(channel.getTimeslot())
.build();
mDetectedCallEventsMap.put(channel.getLogicalSlotNumber(), detectedEvent);
detectedEvent = getDecodeEvent(csbk, DecodeEventType.CALL_GROUP, channel,
new IdentifierCollection(csbk.getIdentifiers()));
mDetectedCallEventsMap.put(channel, detectedEvent);
}
else
{
@@ -930,11 +930,10 @@ public class DMRDecoderState extends TimeslotDecoderState
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
break;
case MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION:
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(csbk.getTimestamp())
DecodeEvent affiliateEvent = DMRDecodeEvent.builder(DecodeEventType.AFFILIATE, csbk.getTimestamp())
.details("TALKGROUP AFFILIATION")
.eventDescription(DecodeEventType.AFFILIATE.toString())
.identifiers(getMergedIdentifierCollection(csbk.getIdentifiers()))
.timeslot(csbk.getTimeslot())
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
.timeslot(getTimeslot())
.build();
broadcast(affiliateEvent);
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL, getTimeslot()));
@@ -945,6 +944,24 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, DMRChannel channel,
IdentifierCollection identifierCollection) {
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
.channel(channel)
.details(csbk.getOpcode().getLabel())
.identifiers(identifierCollection)
.timeslot(getTimeslot())
.build();
}
private DecodeEvent getDecodeEvent(CSBKMessage csbk, DecodeEventType decodeEventType, String details) {
return DMRDecodeEvent.builder(decodeEventType, csbk.getTimestamp())
.identifiers(new IdentifierCollection(csbk.getIdentifiers()))
.timeslot(getTimeslot())
.details(details)
.build();
}
/**
* Creates a copy of the current identifier collection, removes any USER class identifiers and loads the identifiers
* argument values into the collection.
@@ -1043,6 +1060,15 @@ public class DMRDecoderState extends TimeslotDecoderState
{
switch(message.getOpcode())
{
case FULL_ENCRYPTION_PARAMETERS:
if(message instanceof io.github.dsheirer.module.decode.dmr.message.data.lc.full.EncryptionParameters ep)
{
if(mCurrentCallEvent != null)
{
mCurrentCallEvent.setDetails(ep.getDetails());
}
}
break;
case SHORT_CAPACITY_PLUS_REST_CHANNEL_NOTIFICATION:
if(message instanceof CapacityPlusRestChannel)
{
@@ -1066,11 +1092,9 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
break;
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
if(message instanceof CapacityPlusGroupVoiceChannelUser cpgvcu)
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
if(message instanceof MotorolaGroupVoiceChannelUser cpgvcu)
{
updateRestChannel(cpgvcu.getRestChannel());
if(isTerminator)
{
getIdentifierCollection().remove(Role.FROM);
@@ -1191,10 +1215,9 @@ public class DMRDecoderState extends TimeslotDecoderState
MutableIdentifierCollection ic = new MutableIdentifierCollection(getIdentifierCollection().getIdentifiers());
ic.update(gpsInformation.getGPSLocation());
DecodeEvent gpsEvent = DMRDecodeEvent.builder(message.getTimestamp())
.eventDescription(DecodeEventType.GPS.toString())
DecodeEvent gpsEvent = DMRDecodeEvent.builder(DecodeEventType.GPS, message.getTimestamp())
.identifiers(ic)
.timeslot(message.getTimeslot())
.timeslot(getTimeslot())
.details("LOCATION:" + gpsInformation.getGPSLocation())
.build();
@@ -1210,6 +1233,41 @@ public class DMRDecoderState extends TimeslotDecoderState
}
}
/**
* Updates the current call with encryption information.
* @param encryptionParameters decoded from the Voice Frame F
* @param isGroup true for group or false for individual call.
*/
private void updateEncryptedCall(EncryptionParameters encryptionParameters, boolean isGroup, long timestamp)
{
if(mCurrentCallEvent != null)
{
String details = mCurrentCallEvent.getDetails();;
if(details == null)
{
details = encryptionParameters.toString();
}
else if(!details.contains(encryptionParameters.toString()) && !details.contains("ENCRYPTION"))
{
details += " " + encryptionParameters;
}
mCurrentCallEvent.setDetails(details);
}
else
{
mCurrentCallEvent = DMRDecodeEvent.builder(isGroup ? DecodeEventType.CALL_GROUP_ENCRYPTED :
DecodeEventType.CALL_ENCRYPTED, timestamp)
.channel(getCurrentChannel())
.details(encryptionParameters.toString())
.identifiers(getIdentifierCollection().copyOf())
.timeslot(getTimeslot())
.build();
broadcast(mCurrentCallEvent);
}
}
/**
* Updates or creates a current call event.
*
@@ -1223,20 +1281,19 @@ public class DMRDecoderState extends TimeslotDecoderState
if(mCurrentCallEvent == null)
{
mCurrentCallEvent = DMRDecodeEvent.builder(timestamp)
mCurrentCallEvent = DMRDecodeEvent.builder(type, timestamp)
.channel(getCurrentChannel())
.eventDescription(type.toString())
.details(details)
.identifiers(getIdentifierCollection().copyOf())
.timeslot(getTimeslot())
.build();
broadcast(mCurrentCallEvent);
}
else
{
if(type != DecodeEventType.CALL)
if(mCurrentCallEvent.getDetails() == null)
{
mCurrentCallEvent.setEventDescription(type.toString());
mCurrentCallEvent.setDetails(details);
}

View File

@@ -44,6 +44,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceSuperFrameProcessor;
import io.github.dsheirer.sample.Listener;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +61,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
{
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
private DecodeConfigDMR mConfigDMR;
private VoiceSuperFrameProcessor mSuperFrameProcessor1 = new VoiceSuperFrameProcessor();
private VoiceSuperFrameProcessor mSuperFrameProcessor2 = new VoiceSuperFrameProcessor();
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
private MBCAssembler mMBCAssembler = new MBCAssembler();
@@ -89,19 +93,53 @@ public class DMRMessageProcessor implements Listener<IMessage>
@Override
public void receive(IMessage message)
{
//Enrich messages that carry DMR Logical Slot Number channels with LCN to frequency mappings
if(message instanceof FullLCMessage flc)
{
if(flc.getTimeslot() == 1)
{
mSuperFrameProcessor1.process(flc);
}
else
{
mSuperFrameProcessor2.process(flc);
}
}
else if(message instanceof VoiceMessage voiceMessage)
{
if(voiceMessage.getTimeslot() == 1)
{
mSuperFrameProcessor1.process(voiceMessage);
}
else
{
mSuperFrameProcessor2.process(voiceMessage);
}
}
else if(message instanceof DMRBurst dmrBurst && dmrBurst.isValid())
{
if(dmrBurst.getTimeslot() == 1)
{
mSuperFrameProcessor1.reset();
}
else
{
mSuperFrameProcessor2.reset();
}
}
//Enrich messages that carry DMR Logical Channel Numbers with LCN to frequency mappings
if(message instanceof ITimeslotFrequencyReceiver)
{
ITimeslotFrequencyReceiver receiver = (ITimeslotFrequencyReceiver)message;
int[] lsns = receiver.getLogicalTimeslotNumbers();
int[] lcns = receiver.getLogicalChannelNumbers();
List<TimeslotFrequency> timeslotFrequencies = new ArrayList<>();
for(int lsn: lsns)
for(int lcn: lcns)
{
if(mTimeslotFrequencyMap.containsKey(lsn))
if(mTimeslotFrequencyMap.containsKey(lcn))
{
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lsn));
timeslotFrequencies.add(mTimeslotFrequencyMap.get(lcn));
}
}

View File

@@ -1,23 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>
* * *****************************************************************************
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr;
@@ -41,14 +38,13 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusCo
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ConnectPlusTrafficChannel;
import io.github.dsheirer.module.decode.dmr.message.type.Model;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Tracks the network configuration details of a DMR network from the broadcast messages
@@ -250,7 +246,7 @@ public class DMRNetworkConfigurationMonitor
ConnectPlusNeighborReport cpnr = (ConnectPlusNeighborReport)csbk;
mNeighborSites.addAll(cpnr.getNeighbors());
}
if(mBrand == null)
if(mBrand == null || mBrand != BRAND_MOTOROLA_CONNECT_PLUS)
{
mBrand = BRAND_MOTOROLA_CONNECT_PLUS;
}
@@ -279,7 +275,7 @@ public class DMRNetworkConfigurationMonitor
*/
private void addDmrChannel(DMRChannel dmrChannel)
{
mObservedChannelMap.put(dmrChannel.getLogicalSlotNumber(), dmrChannel);
mObservedChannelMap.put(dmrChannel.getValue(), dmrChannel);
}
public void reset()

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2022 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -55,9 +55,6 @@ import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.tuner.channel.rotation.DisableChannelRotationMonitorRequest;
import io.github.dsheirer.source.tuner.channel.rotation.FrequencyLockChangeRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -66,6 +63,8 @@ import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Monitors channel grant and channel grant update messages to allocate traffic channels to capture
@@ -99,7 +98,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
private List<Channel> mManagedTrafficChannels;
private Map<Long,Channel> mAllocatedTrafficChannelFrequencyMap = new ConcurrentHashMap<>();
private Map<Integer,DMRChannelGrantEvent> mLSNGrantEventMap = new ConcurrentHashMap<>();
private Map<DMRChannel,DMRChannelGrantEvent> mChannelGrantEventMap = new ConcurrentHashMap<>();
private Listener<ChannelEvent> mChannelEventListener;
private Listener<IDecodeEvent> mDecodeEventListener;
@@ -165,7 +164,7 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
for(int x = 0; x < maxTrafficChannels; x++)
{
Channel trafficChannel = new Channel("TRAFFIC", ChannelType.TRAFFIC);
Channel trafficChannel = new Channel("T-" + mParentChannel.getName(), ChannelType.TRAFFIC);
trafficChannel.setAliasListName(mParentChannel.getAliasListName());
trafficChannel.setSystem(mParentChannel.getSystem());
trafficChannel.setSite(mParentChannel.getSite());
@@ -323,20 +322,18 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
public void processChannelGrant(DMRChannel channel, IdentifierCollection identifierCollection,
Opcode opcode, long timestamp, boolean encrypted)
{
int lsn = channel.getLogicalSlotNumber();
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
DecodeEventType decodeEventType = getEventType(opcode, identifierCollection, encrypted);
if(isStale(event, timestamp, identifierCollection)) //Create new event
{
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
.channel(channel)
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString())
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(lsn, event);
mChannelGrantEventMap.put(channel, event);
}
else //Update current event
@@ -350,14 +347,13 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
event.end(timestamp);
event = DMRChannelGrantEvent.channelGrantBuilder(timestamp)
event = DMRChannelGrantEvent.channelGrantBuilder(decodeEventType, timestamp)
.channel(channel)
.eventDescription(getEventType(opcode, identifierCollection, encrypted).toString() + " - Continue")
.details("CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.details("CONTINUE - CHANNEL GRANT" + (encrypted ? " ENCRYPTED" : ""))
.identifiers(identifierCollection)
.build();
mLSNGrantEventMap.put(lsn, event);
mChannelGrantEventMap.put(channel, event);
broadcast(event);
}
}
@@ -390,15 +386,6 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
{
if(mIgnoreDataCalls && opcode.isDataChannelGrantOpcode())
{
if(event.getEventDescription() == null)
{
event.setEventDescription(getEventType(opcode, identifierCollection, encrypted) + IGNORED);
}
else if(!event.getEventDescription().endsWith(IGNORED))
{
event.setEventDescription(event.getEventDescription() + IGNORED);
}
if(event.getDetails() == null)
{
event.setDetails(DATA_CALL_IGNORED);
@@ -654,37 +641,37 @@ public class DMRTrafficChannelManager extends TrafficChannelManager implements I
*/
private void removeCallEvents(long frequency)
{
List<Integer> lsnsToRemove = new ArrayList<>();
List<DMRChannel> channelsToRemove = new ArrayList<>();
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
{
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
if(entry.getKey().getDownlinkFrequency() == frequency)
{
lsnsToRemove.add(entry.getKey());
channelsToRemove.add(entry.getKey());
}
}
for(Integer lsn: lsnsToRemove)
for(DMRChannel channel: channelsToRemove)
{
mLSNGrantEventMap.remove(lsn);
mChannelGrantEventMap.remove(channel);
}
}
private void updateCallEventDetails(long frequency, String detailFragment)
{
List<Integer> lsnsToUpdate = new ArrayList<>();
List<DMRChannel> channelsToUpdate = new ArrayList<>();
for(Map.Entry<Integer,DMRChannelGrantEvent> entry: mLSNGrantEventMap.entrySet())
for(Map.Entry<DMRChannel,DMRChannelGrantEvent> entry: mChannelGrantEventMap.entrySet())
{
if(entry.getValue().getChannelDescriptor().getDownlinkFrequency() == frequency)
{
lsnsToUpdate.add(entry.getKey());
channelsToUpdate.add(entry.getKey());
}
}
for(Integer lsn: lsnsToUpdate)
for(DMRChannel channel: channelsToUpdate)
{
DMRChannelGrantEvent event = mLSNGrantEventMap.get(lsn);
DMRChannelGrantEvent event = mChannelGrantEventMap.get(channel);
if(event != null)
{

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2021 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,17 +32,16 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChann
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.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.CapacityPlusGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.MotorolaGroupVoiceChannelUser;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.sample.Listener;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* DMR AMBE Frame recorder generates call sequence recordings containing JSON representations of audio
* frames, optional encryption and call identifiers.
@@ -206,8 +205,8 @@ public class DMRCallSequenceRecorder extends MBECallSequenceRecorder
{
switch(message.getOpcode())
{
case FULL_CAPACITY_PLUS_GROUP_VOICE_CHANNEL_USER:
if(message instanceof CapacityPlusGroupVoiceChannelUser cpvcu)
case FULL_MOTOROLA_GROUP_VOICE_CHANNEL_USER:
if(message instanceof MotorolaGroupVoiceChannelUser cpvcu)
{
if(mCallSequence == null)
{

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,6 +26,8 @@ import io.github.dsheirer.identifier.Role;
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.protocol.Protocol;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.Validate;
/**
@@ -33,8 +35,9 @@ import org.apache.commons.lang3.Validate;
*
* Note: timeslots are tracked as 1 and 2
*/
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor
public abstract class DMRChannel extends IntegerIdentifier implements IChannelDescriptor, ITimeslotFrequencyReceiver
{
private TimeslotFrequency mTimeslotFrequency;
private int mTimeslot;
/**
@@ -56,13 +59,23 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
}
/**
* Repeater number for the channel.
* Repeater number aka Logical Channel Number (LCN) for the channel.
*/
public int getRepeater()
public int getChannelNumber()
{
return getValue();
}
/**
* Convenience method to format the single channel number as an array for TimeslotFrequency matching.
* @return channel number as an integer array.
*/
public int[] getLogicalChannelNumbers()
{
return new int[]{getChannelNumber()};
}
/**
* Timeslot for the channel.
* @return timeslot as zero-based index with values in range: 0 or 1.
@@ -72,36 +85,6 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
return mTimeslot;
}
/**
* Logical slot number for this channel. LSN is a 1-based index value where repeater one, timeslot 1 is
* LSN 1, timeslot 2 is LSN 2, etc.
*
* Formula: LSN = ((channel - 1) * 2) + timeslot
*
* @return logical slot number, a 1-based index value
*/
public int getLogicalSlotNumber()
{
int repeater = getRepeater();
if(repeater > 0)
{
return ((repeater - 1) * 2) + getTimeslot();
}
return 0;
}
/**
* Returns an array of length 1 containing this channel's logical slot number
*/
public int[] getLSNArray()
{
int[] logicalSlotNumbers = new int[1];
logicalSlotNumbers[0] = getLogicalSlotNumber();
return logicalSlotNumbers;
}
/**
* Number of timeslots for the DMR channel.
* @return 2 always.
@@ -121,6 +104,13 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
return true;
}
@Override
public String toString()
{
return "CHAN:" + getChannelNumber() + ":" + getTimeslot();
}
/**
* Not implemented
*/
@@ -138,4 +128,72 @@ public abstract class DMRChannel extends IntegerIdentifier implements IChannelDe
{
throw new IllegalArgumentException("This method is not supported");
}
@Override
public long getDownlinkFrequency()
{
if(mTimeslotFrequency != null)
{
return mTimeslotFrequency.getDownlinkFrequency();
}
return 0;
}
@Override
public long getUplinkFrequency()
{
if(mTimeslotFrequency != null)
{
return mTimeslotFrequency.getUplinkFrequency();
}
return 0;
}
/**
* Sets the lsn to frequency mapper value.
* @param timeslotFrequency to set
*/
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
{
mTimeslotFrequency = timeslotFrequency;
}
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(timeslotFrequency.getChannelNumber() == getChannelNumber())
{
setTimeslotFrequency(timeslotFrequency);
return;
}
}
}
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof DMRChannel that))
{
return false;
}
if(!super.equals(o))
{
return false;
}
return getTimeslot() == that.getTimeslot();
}
@Override
public int hashCode()
{
return Objects.hash(super.hashCode(), getTimeslot());
}
}

View File

@@ -1,90 +0,0 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.channel;
/**
* DMR logical channel. This channel uses a logical channel number and a timeslot.
*/
public class DMRLogicalChannel extends DMRChannel
{
private TimeslotFrequency mTimeslotFrequency;
/**
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
* calculated logical slot value for visual compatibility for users.
*
* @param channel number or repeater number
* @param logicalSlotNumber - zero based index.
*/
public DMRLogicalChannel(int channel, int timeslot)
{
super(channel, timeslot);
}
/**
* Downlink frequency
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
*/
@Override
public long getDownlinkFrequency()
{
if(mTimeslotFrequency != null)
{
return mTimeslotFrequency.getDownlinkFrequency();
}
return 0;
}
/**
* Uplink frequency
* @return value in Hertz, or 0 if this channel doesn't have a timeslot frequency mapping
*/
@Override
public long getUplinkFrequency()
{
if(mTimeslotFrequency != null)
{
return mTimeslotFrequency.getUplinkFrequency();
}
return 0;
}
/**
* Sets the timeslot frequency mapping
* @param timeslotFrequency
*/
public void setTimeslotFrequency(TimeslotFrequency timeslotFrequency)
{
mTimeslotFrequency = timeslotFrequency;
}
/**
* Formatted channel number
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("LSN:").append(getLogicalSlotNumber());
// sb.append(" LCN:").append(getValue());
return sb.toString();
}
}

View File

@@ -0,0 +1,72 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.channel;
/**
* DMR Logical Slot Number (LSN) channel.
*
* Note: LSN 1-16 are represented in memory using the following channel and timeslot values:
*
* LSN: CHANNEL/TIMESLOT
* 1: 1/1
* 2: 1/2
* 3: 2/1
* 4: 2/2
* 5: 3/1
* 6: 3/2
* 7: 4/1
* 8: 4/2
* 9: 5/1
* 10: 5/2
* 11: 6/1
* 12: 6/2
* 13: 7/1
* 14: 7/2
* 15: 8/1
* 16: 8/2
*/
public class DMRLsn extends DMRChannel
{
/**
* Constructs an instance
*
* @param lsn in range 1 - 16
*/
public DMRLsn(int lsn)
{
super(((lsn - 1) / 2) + 1, ((lsn - 1) % 2) + 1);
}
@Override
public String toString()
{
return "LSN:" + getLsn() + " LCN:" + getChannelNumber();
}
/**
* Logical Slot Number
* @return lsn
*/
public int getLsn()
{
return ((getChannelNumber() - 1) * 2) + getTimeslot();
}
}

View File

@@ -1,16 +1,35 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.channel;
/**
* DMR Tier III Trunking Channel
* DMR Tier III Trunking channel. This channel uses a logical channel number and a timeslot.
*/
public class DMRTier3Channel extends DMRLogicalChannel
public class DMRTier3Channel extends DMRChannel
{
/**
* Constructs an instance. Note: radio reference uses a one based index, so we add a value of one to the
* calculated logical slot value for visual compatibility for users.
*
* @param channel number or repeater number
* @param timeslot
* @param timeslot either 1 or 2.
*/
public DMRTier3Channel(int channel, int timeslot)
{
@@ -18,23 +37,22 @@ public class DMRTier3Channel extends DMRLogicalChannel
}
/**
* Logical slot number for this channel.
*
* Formula: LSN = (channel * 2) + timeslot
*
* @return logical slot number, a 1-based index value
* Tier III Channel ID
* @return channel ID
*/
@Override
public int getLogicalSlotNumber()
public int getChannelId()
{
int repeater = getRepeater();
if(repeater > 0)
{
return ((repeater) * 2) + getTimeslot();
}
return 0;
return getChannelNumber() * 2 + getTimeslot();
}
/**
* Formatted channel number
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(" LCN:").append(getChannelNumber());
sb.append(" CHANID:").append(getChannelId());
return sb.toString();
}
}

View File

@@ -0,0 +1,42 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.channel;
/**
* DMR Rest Channel logical slot number (LSN).
*/
public class DmrRestLsn extends DMRLsn
{
/**
* Constructs an instance
*
* @param lsn in range 1 - 16
*/
public DmrRestLsn(int lsn)
{
super(lsn);
}
@Override
public String toString()
{
return "REST:" + getLsn();
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,9 +24,9 @@ import java.util.List;
public interface ITimeslotFrequencyReceiver
{
/**
* Provides the logical slot number(s) that require a matching timeslot frequency mapping
* Provides the logical channel number(s) that require a matching timeslot frequency mapping
*/
public int[] getLogicalTimeslotNumbers();
public int[] getLogicalChannelNumbers();
/**
* Applies the list of timeslot frequency mappings to the implementer

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,7 +31,7 @@ import javafx.beans.property.SimpleLongProperty;
import javafx.util.Callback;
/**
* Maps a timeslot number to a pair of channel frequency values
* Maps a logical channel number (LCN) to a pair of channel frequency values
*/
public class TimeslotFrequency
{
@@ -73,7 +73,7 @@ public class TimeslotFrequency
* Downlink Frequency property
*/
@JsonIgnore
public LongProperty downlinkFrequencyPropertyProperty()
public LongProperty downlinkFrequencyProperty()
{
return mDownlinkFrequencyProperty;
}
@@ -100,7 +100,10 @@ public class TimeslotFrequency
}
/**
* Logical slot number (LSN) as a 1-index based counter
* Logical channel number (LCN) as a 1-index based counter
*
* Note: lsn (logical slot number) is a legacy name for this field. The field currently holds the LCN or logical
* channel number. To avoid breaking serialization, we leave this field labeled as lsn.
*/
@JacksonXmlProperty(isAttribute = true, localName = "lsn")
public int getNumber()
@@ -109,12 +112,52 @@ public class TimeslotFrequency
}
/**
* Sets the logical slot number (LSN) as a 1-index start
* @param lsn where LSN 1 is the first slot, 2 the second, etc
* Logical channel number which is the same value as getNumber.
* @return
*/
public void setNumber(int lsn)
@JsonIgnore
public int getChannelNumber()
{
mNumberProperty.set(lsn);
return getNumber();
}
@JsonIgnore
public String getLogicalSlotNumbers()
{
StringBuilder sb = new StringBuilder();
int channel = getChannelNumber();
if(channel > 0)
{
if(channel < 32) //Connect Plus uses 5-bits for LSN for a maximum value of 31
{
//Cap+ & Con+ Logical Slot Numbers
int lsn1 = (channel - 1) * 2 + 1;
int lsn2 = (channel - 1) * 2 + 2;
sb.append("LSN:").append(lsn1).append("/").append(lsn2).append(" or ");
}
//Tier III channel IDs
int chanId1 = channel * 2 + 1;
int chanId2 = channel * 2 + 2;
sb.append("Tier3 Chan:").append(chanId1).append("/").append(chanId2);
}
else
{
sb.append("(empty)");
}
return sb.toString();
}
/**
* Sets the logical channel number (LCN) as a 1-index start
* @param lcn where LCN 1 is logical slot numbers (LSN) 1 and 2, LCN2=LSN3/4, etc.
*/
public void setNumber(int lcn)
{
mNumberProperty.set(lcn);
}
/**
@@ -160,7 +203,7 @@ public class TimeslotFrequency
@Override
public String toString()
{
return "TIMESLOT LSN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
return "LCN:" + getNumber() + " DOWNLINK:" + getDownlinkFrequency();
}
/**
@@ -168,7 +211,7 @@ public class TimeslotFrequency
*/
public static Callback<TimeslotFrequency,Observable[]> extractor()
{
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyPropertyProperty(),
return (TimeslotFrequency tf) -> new Observable[] {tf.getNumberProperty(), tf.downlinkFrequencyProperty(),
tf.uplinkFrequencyProperty(), tf.getDownlinkMHz(), tf.getUplinkMHz()};
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +14,13 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.event;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.event.DecodeEventType;
/**
* DMR Channel Grant Event.
@@ -33,11 +33,12 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
/**
* Constructs an instance
* @param decodeEventType for the event
* @param timestamp for the event
*/
public DMRChannelGrantEvent(long timestamp)
public DMRChannelGrantEvent(DecodeEventType decodeEventType, long timestamp)
{
super(timestamp);
super(decodeEventType, timestamp);
}
/**
@@ -62,9 +63,9 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(long timeStart)
public static DMRChannelGrantDecodeEventBuilder channelGrantBuilder(DecodeEventType decodeEventType, long timeStart)
{
return new DMRChannelGrantDecodeEventBuilder(timeStart);
return new DMRChannelGrantDecodeEventBuilder(decodeEventType, timeStart);
}
/**
@@ -74,7 +75,7 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
{
protected long mTimeStart;
protected long mDuration;
protected String mEventDescription;
protected DecodeEventType mDecodeEventType;
protected IdentifierCollection mIdentifierCollection;
protected DMRChannel mChannel;
protected String mDetails;
@@ -85,8 +86,9 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
*
* @param timeStart
*/
public DMRChannelGrantDecodeEventBuilder(long timeStart)
public DMRChannelGrantDecodeEventBuilder(DecodeEventType decodeEventType, long timeStart)
{
mDecodeEventType = decodeEventType;
mTimeStart = timeStart;
}
@@ -121,7 +123,7 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
/**
* Sets the channel descriptor for this event
* @param channelDescriptor
* @param channel
*/
public DMRChannelGrantDecodeEventBuilder channel(DMRChannel channel)
{
@@ -129,16 +131,6 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
return this;
}
/**
* Sets the event description text
* @param description of the event
*/
public DMRChannelGrantDecodeEventBuilder eventDescription(String description)
{
mEventDescription = description;
return this;
}
/**
* Sets the identifier collection.
* @param identifierCollection containing optional identifiers like TO, FROM, frequency and
@@ -165,12 +157,11 @@ public class DMRChannelGrantEvent extends DMRDecodeEvent
*/
public DMRChannelGrantEvent build()
{
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mTimeStart);
DMRChannelGrantEvent decodeEvent = new DMRChannelGrantEvent(mDecodeEventType, mTimeStart);
decodeEvent.setChannelDescriptor(mChannel);
decodeEvent.setTimeslot(mChannel.getTimeslot());
decodeEvent.setDetails(mDetails);
decodeEvent.setDuration(mDuration);
decodeEvent.setEventDescription(mEventDescription);
decodeEvent.setIdentifierCollection(mIdentifierCollection);
return decodeEvent;
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +14,13 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.event;
import io.github.dsheirer.module.decode.event.DecodeEvent;
import io.github.dsheirer.module.decode.event.DecodeEventType;
import io.github.dsheirer.protocol.Protocol;
/**
@@ -32,9 +32,9 @@ public class DMRDecodeEvent extends DecodeEvent
* Constucts a decode event
* @param start
*/
public DMRDecodeEvent(long start)
public DMRDecodeEvent(DecodeEventType decodeEventType, long start)
{
super(start);
super(decodeEventType, start);
setProtocol(Protocol.DMR);
}
@@ -43,9 +43,9 @@ public class DMRDecodeEvent extends DecodeEvent
* @param timeStart for the event
* @return builder
*/
public static DecodeEventBuilder builder(long timeStart)
public static DecodeEventBuilder builder(DecodeEventType decodeEventType, long timeStart)
{
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(timeStart);
DecodeEventBuilder decodeEventBuilder = new DecodeEventBuilder(decodeEventType, timeStart);
decodeEventBuilder.protocol(Protocol.DMR);
return decodeEventBuilder;
}

View File

@@ -1,5 +1,25 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.module.decode.dmr.message;
import io.github.dsheirer.bits.BinaryMessage;
import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
/**
@@ -53,4 +73,13 @@ public abstract class DMRBurst extends DMRMessage
{
return mSyncPattern;
}
/**
* Extracts the sync payload from between the two payload fragments.
* @return binary message containing just the sync payload bits (64).
*/
public BinaryMessage getSyncPayload()
{
return getMessage().get(SYNC_START, PAYLOAD_2_START);
}
}

View File

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

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,8 +33,8 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessageFactory
import io.github.dsheirer.module.decode.dmr.message.data.header.ConfirmedDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.DataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.DefinedShortDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.PiHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.RawShortDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.ResponseDataHeader;
@@ -43,9 +43,9 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.StatusDataHeader
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.UnconfirmedDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.VoiceHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.hytera.HyteraDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MNISProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaProprietaryDataHeader;
import io.github.dsheirer.module.decode.dmr.message.data.header.motorola.MotorolaDataEncryptionHeader;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
import io.github.dsheirer.module.decode.dmr.message.data.usb.USBData;
@@ -98,7 +98,7 @@ public class DMRDataMessageFactory
return new MBCHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case CHANNEL_CONTROL_ENC_HEADER:
case PI_HEADER:
return new HeaderMessage(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
return new PiHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case VOICE_HEADER:
return new VoiceHeader(pattern, getPayload(message), cach, slotType, timestamp, timeslot);
case DATA_ENC_HEADER:
@@ -132,16 +132,16 @@ public class DMRDataMessageFactory
}
else
{
MotorolaProprietaryDataHeader mprdh = new MotorolaProprietaryDataHeader(pattern,
MotorolaDataEncryptionHeader mprdh = new MotorolaDataEncryptionHeader(pattern,
payload, cach, slotType, timestamp, timeslot);
mprdh.setValid(valid);
return mprdh;
}
case HYTERA_68:
HyteraProprietaryDataHeader hpdh = new HyteraProprietaryDataHeader(pattern, payload,
cach, slotType, timestamp, timeslot);
hpdh.setValid(valid);
return hpdh;
HyteraDataEncryptionHeader hsdh = new HyteraDataEncryptionHeader(pattern, payload,
cach, slotType, timestamp, timeslot);
hsdh.setValid(valid);
return hsdh;
default:
ProprietaryDataHeader pdh = new ProprietaryDataHeader(pattern, payload, cach,
slotType, timestamp, timeslot);

View File

@@ -34,7 +34,7 @@ import java.util.List;
*/
public abstract class DataMessageWithLinkControl extends DataMessage
{
private LCMessage mLCMessage;
protected LCMessage mLCMessage;
/**
* Constructs an instance.

View File

@@ -56,6 +56,10 @@ public class DataBlock1Rate extends DataBlock
{
StringBuilder sb = new StringBuilder();
sb.append("CC:").append(getSlotType().getColorCode());
if(hasRAS())
{
sb.append(" RAS:").append(getBPTCReservedBits());
}
if(!isValid())
{
sb.append(" [CRC ERROR]");

View File

@@ -56,6 +56,10 @@ public class DataBlock1_2Rate extends DataBlock
{
StringBuilder sb = new StringBuilder();
sb.append("CC:").append(getSlotType().getColorCode());
if(hasRAS())
{
sb.append(" RAS:").append(getBPTCReservedBits());
}
if(!isValid())
{
sb.append(" [CRC ERROR]");

View File

@@ -57,6 +57,10 @@ public class DataBlock3_4Rate extends DataBlock
{
StringBuilder sb = new StringBuilder();
sb.append("CC:").append(getSlotType().getColorCode());
if(hasRAS())
{
sb.append(" RAS:").append(getBPTCReservedBits());
}
if(!isValid())
{
sb.append(" [CRC ERROR]");

View File

@@ -27,7 +27,6 @@ import java.util.TreeMap;
/**
* Control Signalling Block (CSBK) Opcode enumeration
*
* ETSI TS 102 361-2 DMR Voice & Generic Services, Annex B
* ETSI TS 102 361-4 DMR Trunking Protocol, Annex B
*/
@@ -39,7 +38,7 @@ public enum Opcode
STANDARD_CHANNEL_TIMING(Vendor.STANDARD, 7, "CHANNEL TIMING"),
STANDARD_ALOHA(Vendor.STANDARD, 25, "ALOHA"),
STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER(Vendor.STANDARD, 26, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER(Vendor.STANDARD, 27, "UNIFIED DATA TRANSPORT INBOUND HEADER"),
STANDARD_AHOY(Vendor.STANDARD, 28, "AHOY"),
STANDARD_ACTIVATION(Vendor.STANDARD, 30, "ACTIVATION"),
STANDARD_RANDOM_ACCESS_SERVICE_REQUEST(Vendor.STANDARD, 31, "RANDOM ACCESS SERVICE REQUEST"),
@@ -91,6 +90,8 @@ public enum Opcode
HYTERA_08_ACKNOWLEDGE(Vendor.HYTERA_8, 32, "HYTERA 08 ACKNOWLEDGE"),
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "HYTERA 08 ANNOUNCEMENT"),
//CSBKO 44 & 47 observed on Tier3 interleaved in voice group call terminator sequence. 44 was continuously transmitted
//and 47 was only transmitted 3x times in succession in the middle of the terminators and CSBKO 44 messages.
HYTERA_08_CSBKO_44(Vendor.HYTERA_8, 44, "HYTERA 08 CSBKO 44"),
HYTERA_08_CSBKO_47(Vendor.HYTERA_8, 47, "HYTERA 08 CSBKO 47"),
@@ -102,13 +103,18 @@ public enum Opcode
HYTERA_68_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA 68 XPT PREAMBLE"),
HYTERA_68_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA 68 CSBKO 62"),
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
private Vendor mVendor;
private int mValue;
private String mLabel;
private final Vendor mVendor;
private final int mValue;
private final String mLabel;
/**
* Constructor
* @param vendor for the opcode
* @param value for the opcode
* @param label to display for the opcode
*/
Opcode(Vendor vendor, int value, String label)
{
mVendor = vendor;
@@ -151,19 +157,86 @@ public enum Opcode
*/
public boolean isDataChannelGrantOpcode()
{
return DATA_CHANNEL_GRANT_OPCODES.contains(this);
return DATA_CHANNEL_GRANTS.contains(this);
}
/**
* Data Channel Grant opcodes
* Data channel grant opcodes
*/
public static final EnumSet<Opcode> DATA_CHANNEL_GRANT_OPCODES = EnumSet.of(
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM,
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM,
STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT,
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM,
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM);
public static final EnumSet<Opcode> DATA_CHANNEL_GRANTS = EnumSet.of(STANDARD_PRIVATE_DATA_CHANNEL_GRANT_MULTI_ITEM,
STANDARD_PRIVATE_DATA_CHANNEL_GRANT_SINGLE_ITEM, STANDARD_DUPLEX_PRIVATE_DATA_CHANNEL_GRANT,
STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_MULTI_ITEM, STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM);
/**
* Data opcodes
*/
public static final EnumSet<Opcode> DATA_OPCODES = EnumSet.of(STANDARD_UNIFIED_DATA_TRANSPORT_OUTBOUND_HEADER,
STANDARD_UNIFIED_DATA_TRANSPORT_INBOUND_HEADER, STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER,
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER, STANDARD_PREAMBLE);
/**
* Mobile request and response opcodes
*/
public static final EnumSet<Opcode> MOBILE_REQUEST_RESPONSE = EnumSet.of(STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_REQUEST,
STANDARD_RANDOM_ACCESS_SERVICE_REQUEST);
/**
* Network request and response and announcement opcodes
*/
public static final EnumSet<Opcode> NETWORK_REQUEST_RESPONSE = EnumSet.of(STANDARD_FEATURE_NOT_SUPPORTED,
STANDARD_UNIT_TO_UNIT_VOICE_SERVICE_RESPONSE, STANDARD_AHOY, STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_TSCC,
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_TSCC, STANDARD_ACKNOWLEDGE_RESPONSE_OUTBOUND_PAYLOAD,
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD, STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE, STANDARD_CLEAR,
STANDARD_MOVE_TSCC, STANDARD_CHANNEL_TIMING,
STANDARD_ALOHA, STANDARD_ACTIVATION, STANDARD_ANNOUNCEMENT, STANDARD_MAINTENANCE, STANDARD_PROTECT);
/**
* Voice channel grant opcodes
*/
public static final EnumSet<Opcode> VOICE_CHANNEL_GRANTS = EnumSet.of(STANDARD_PRIVATE_VOICE_CHANNEL_GRANT,
STANDARD_TALKGROUP_VOICE_CHANNEL_GRANT, STANDARD_BROADCAST_TALKGROUP_VOICE_CHANNEL_GRANT,
STANDARD_DUPLEX_PRIVATE_VOICE_CHANNEL_GRANT);
/**
* Hytera opcodes
*/
public static final EnumSet<Opcode> HYTERA = EnumSet.of(HYTERA_08_ACKNOWLEDGE, HYTERA_08_ANNOUNCEMENT,
HYTERA_08_CSBKO_44, HYTERA_08_CSBKO_47, HYTERA_68_XPT_SITE_STATE, HYTERA_68_ALOHA,
HYTERA_68_ACKNOWLEDGE, HYTERA_68_ANNOUNCEMENT, HYTERA_68_XPT_PREAMBLE, HYTERA_68_CSBKO_62);
/**
* Motorola Capacity Max opcodes
*/
public static final EnumSet<Opcode> MOTOROLA_CAPACITY_MAX = EnumSet.of(MOTOROLA_CAPMAX_ALOHA);
/**
* Motorola Capacity Plus opcodes
*/
public static final EnumSet<Opcode> MOTOROLA_CAPACITY_PLUS = EnumSet.of(MOTOROLA_CAPPLUS_CALL_ALERT,
MOTOROLA_CAPPLUS_CALL_ALERT_ACK, MOTOROLA_CAPPLUS_DATA_WINDOW_ANNOUNCEMENT,
MOTOROLA_CAPPLUS_DATA_WINDOW_GRANT, MOTOROLA_CAPPLUS_NEIGHBOR_REPORT, MOTOROLA_CAPPLUS_CSBKO_60,
MOTOROLA_CAPPLUS_PREAMBLE, MOTOROLA_CAPPLUS_SITE_STATUS);
/**
* Motorola Connect Plus opcodes
*/
public static final EnumSet<Opcode> MOTOROLA_CONNECT_PLUS = EnumSet.of(MOTOROLA_CONPLUS_NEIGHBOR_REPORT,
MOTOROLA_CONPLUS_VOICE_CHANNEL_USER, MOTOROLA_CONPLUS_DATA_CHANNEL_GRANT, MOTOROLA_CONPLUS_CSBKO_10,
MOTOROLA_CONPLUS_TERMINATE_CHANNEL_GRANT, MOTOROLA_CONPLUS_CSBKO_16, MOTOROLA_CONPLUS_REGISTRATION_REQUEST,
MOTOROLA_CONPLUS_REGISTRATION_RESPONSE, MOTOROLA_CONPLUS_TALKGROUP_AFFILIATION,
MOTOROLA_CONPLUS_DATA_WINDOW_ANNOUNCEMENT, MOTOROLA_CONPLUS_DATA_WINDOW_GRANT);
/**
* Indicates if the opcode is included in one of the enumset groupings above
* @return
*/
public boolean isGrouped()
{
return DATA_CHANNEL_GRANTS.contains(this) || DATA_OPCODES.contains(this) ||
MOBILE_REQUEST_RESPONSE.contains(this) || NETWORK_REQUEST_RESPONSE.contains(this) ||
VOICE_CHANNEL_GRANTS.contains(this) || HYTERA.contains(this) || MOTOROLA_CAPACITY_MAX.contains(this) ||
MOTOROLA_CAPACITY_PLUS.contains(this) || MOTOROLA_CONNECT_PLUS.contains(this);
}
/**
* Lookup map of vendors and opcode value to opcodes

View File

@@ -22,7 +22,7 @@ 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.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
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.DMRSite;
@@ -39,7 +39,6 @@ import java.util.List;
public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequencyReceiver
{
private static final int[] LC_START_STOP = new int[]{16, 17};
private static final int TIMESLOT = 18;
private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
private static final int ASYNC = 24;
private static final int[] SITE = new int[]{25, 26, 27, 28};
@@ -58,7 +57,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
private static final int[] NEIGHBOR_6_REST = new int[]{76, 77, 78, 79};
private DMRLogicalChannel mRestChannel;
private DmrRestLsn mRestChannel;
private DMRSite mSite;
private List<Identifier> mIdentifiers;
@@ -286,37 +285,23 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
return getMessage().getInt(REST_LSN);
}
/**
* Rest repeater
*/
public int getRestRepeater() { return (int) Math.ceil(getRestLSN() / 2.0); }
/**
* Rest timeslot
* @return timeslot 1 or 2
*/
public int getRestTimeslot()
{
return (getRestLSN() % 2 == 0) ? 2 : 1;
}
/**
* DMR Channel
*/
public DMRLogicalChannel getRestChannel()
public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
}
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalChannelNumbers()
{
return getRestChannel().getLSNArray();
return getRestChannel().getLogicalChannelNumbers();
}
/**
@@ -327,13 +312,7 @@ public class CapacityPlusNeighbors extends CSBKMessage implements ITimeslotFrequ
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getRestChannel().getLogicalSlotNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
}
getRestChannel().apply(timeslotFrequencies);
}
@Override

View File

@@ -22,7 +22,7 @@ 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.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
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.message.CACH;
@@ -39,13 +39,11 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
private static final int[] BYTE = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
private static final int[] TWO_BYTES = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
private static final int[] SEGMENT_INDICATOR = new int[]{16, 17};
private static final int TIMESLOT = 18;
private static final int RESERVED = 19;
private static final int[] REST_LSN = new int[]{20, 21, 22, 23};
private static final int[] REST_LSN = new int[]{19, 20, 21, 22, 23};
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 DMRLogicalChannel mRestChannel;
private DmrRestLsn mRestChannel;
private List<Identifier> mIdentifiers;
/**
@@ -80,7 +78,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
sb.append(" RAS:").append(getBPTCReservedBits());
}
sb.append(" CSBK CAP+ SITE STATUS REST ").append(getRestChannel());
sb.append(" CSBK CAP+ SITE STATUS ").append(getRestChannel());
sb.append(" ").append(getSegmentIndicator());
sb.append(" ");
@@ -277,11 +275,11 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
/**
* Current rest channel for this site.
*/
public DMRLogicalChannel getRestChannel()
public DmrRestLsn getRestChannel()
{
if(mRestChannel == null)
{
mRestChannel = new DMRLogicalChannel(getRestRepeater(), getRestTimeslot());
mRestChannel = new DmrRestLsn(getRestLSN());
}
return mRestChannel;
@@ -297,31 +295,13 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
return getMessage().getInt(REST_LSN);
}
/**
* Rest Channel Repeater
*/
public int getRestRepeater()
{
return (int) Math.ceil(getRestLSN() / 2.0);
}
/**
* Rest Channel Timeslot
*
* @return 1 or 2
*/
public int getRestTimeslot()
{
return (getRestLSN() % 2 == 0) ? 2 : 1;
}
/**
* Logical slot numbers that require slot to frequency mappings.
*/
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalChannelNumbers()
{
return getRestChannel().getLSNArray();
return getRestChannel().getLogicalChannelNumbers();
}
/**
@@ -332,13 +312,7 @@ public class CapacityPlusSiteStatus extends CSBKMessage implements ITimeslotFreq
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(getRestChannel().getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
getRestChannel().setTimeslotFrequency(timeslotFrequency);
}
}
getRestChannel().apply(timeslotFrequencies);
}
@Override

View File

@@ -23,7 +23,7 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
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;
@@ -40,14 +40,13 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
{
private static final int[] TARGET_ADDRESS = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] REPEATER = new int[]{40, 41, 42, 43};
private static final int[] CHANNEL_GRANT_TIMESLOT = new int[]{44};
private static final int[] LOGICAL_SLOT_NUMBER = new int[]{40, 41, 42, 43, 44};
//Analysis: this field correlates to UNKNOWN_FIELD_1(bits: 40-48) in ConnectPlusTerminateChannelGrant.
private static final int[] UNKNOWN_FIELD = new int[]{48, 49, 50, 51, 52, 53, 54, 55};
private RadioIdentifier mTargetRadio;
private DMRLogicalChannel mDMRLogicalChannel;
private DMRLsn mDmrLsn;
private List<Identifier> mIdentifiers;
/**
@@ -110,39 +109,30 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
}
/**
* Channel grant repeater number
* Channel grant logical slot number
*/
public int getRepeater()
public int getLsn()
{
return getMessage().getInt(REPEATER);
}
/**
* Channel grant timeslot
* @return 1 or 2
*/
public int getChannelGrantTimeslot()
{
return getMessage().getInt(CHANNEL_GRANT_TIMESLOT) + 1;
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Don't change this ... always subtract 1 to get the repeater LSN
}
/**
* DMR Channel
*/
public DMRLogicalChannel getChannel()
public DMRLsn getChannel()
{
if(mDMRLogicalChannel == null)
if(mDmrLsn == null)
{
mDMRLogicalChannel = new DMRLogicalChannel(getRepeater(), getChannelGrantTimeslot());
mDmrLsn = new DMRLsn(getLsn());
}
return mDMRLogicalChannel;
return mDmrLsn;
}
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalChannelNumbers()
{
return getChannel().getLSNArray();
return getChannel().getLogicalChannelNumbers();
}
/**
@@ -152,13 +142,7 @@ public class ConnectPlusDataChannelGrant extends CSBKMessage implements ITimeslo
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
{
getChannel().setTimeslotFrequency(timeslotFrequency);
}
}
getChannel().apply(timeslotFrequencies);
}
@Override

View File

@@ -22,7 +22,7 @@ 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.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
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.message.CACH;
@@ -44,11 +44,10 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
private static final int[] MESSAGE_TYPE = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] VERSION = new int[]{24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] UNKNOWN = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 63};
private static final int[] DATA_REPEATER = new int[]{64, 65, 66, 67};
private static final int[] DATA_TIMESLOT = new int[]{68};
57, 58, 59, 60, 61, 62};
private static final int[] LOGICAL_SLOT_NUMBER = new int[]{63, 64, 65, 66, 67};
private DMRLogicalChannel mDataChannel;
private DMRLsn mDataChannel;
private List<Identifier> mIdentifiers;
/**
@@ -100,39 +99,30 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
}
/**
* Data repeater number
* Data logical slot numberrepeater number
*/
public int getDataRepeater()
public int getDataLsn()
{
return getMessage().getInt(DATA_REPEATER);
}
/**
* Data Timeslot
* @return 1 or 2
*/
public int getDataTimeslot()
{
return getMessage().getInt(DATA_TIMESLOT) + 1;
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Leave this as minus 1
}
/**
* DMR Channel where the data is available
*/
public DMRLogicalChannel getDataChannel()
public DMRLsn getDataChannel()
{
if(mDataChannel == null)
{
mDataChannel = new DMRLogicalChannel(getDataRepeater(), getDataTimeslot());
mDataChannel = new DMRLsn(getDataLsn());
}
return mDataChannel;
}
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalChannelNumbers()
{
return getDataChannel().getLSNArray();
return getDataChannel().getLogicalChannelNumbers();
}
/**
@@ -143,13 +133,7 @@ public class ConnectPlusOTAAnnouncement extends CSBKMessage implements ITimeslot
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency : timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getDataChannel().getLogicalSlotNumber())
{
getDataChannel().setTimeslotFrequency(timeslotFrequency);
}
}
getDataChannel().apply(timeslotFrequencies);
}
@Override

View File

@@ -24,7 +24,7 @@ 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.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLsn;
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;
@@ -44,13 +44,12 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
32, 33, 34, 35, 36, 37, 38, 39};
private static final int[] GROUP_ADDRESS = new int[]{40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63};
private static final int[] TRAFFIC_CHANNEL_REPEATER = new int[]{64, 65, 66, 67};
private static final int[] TRAFFIC_CHANNEL_TIMESLOT = new int[]{68};
private static final int[] LOGICAL_SLOT_NUMBER = new int[]{64, 65, 66, 67, 68};
private static final int[] UNKNOWN_FIELD = new int[]{72, 73, 74, 75, 76, 77, 78, 79};
private RadioIdentifier mRadio;
private TalkgroupIdentifier mTalkgroup;
private DMRLogicalChannel mDMRLogicalChannel;
private DMRLsn mDmrLsn;
private List<Identifier> mIdentifiers;
/**
@@ -127,37 +126,28 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
/**
* Traffic channel repeater
*/
public int getTrafficChannelRepeater()
public int getTrafficLsn()
{
return getMessage().getInt(TRAFFIC_CHANNEL_REPEATER);
}
/**
* Traffic channel timeslot
* @return 1 or 2
*/
public int getTrafficChannelTimeslot()
{
return getMessage().getInt(TRAFFIC_CHANNEL_TIMESLOT) + 1;
return getMessage().getInt(LOGICAL_SLOT_NUMBER) - 1; //Always leave as minus 1
}
/**
* DMR Channel
*/
public DMRLogicalChannel getChannel()
public DMRLsn getChannel()
{
if(mDMRLogicalChannel == null)
if(mDmrLsn == null)
{
mDMRLogicalChannel = new DMRLogicalChannel(getTrafficChannelRepeater(), getTrafficChannelTimeslot());
mDmrLsn = new DMRLsn(getTrafficLsn());
}
return mDMRLogicalChannel;
return mDmrLsn;
}
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalChannelNumbers()
{
return getChannel().getLSNArray();
return getChannel().getLogicalChannelNumbers();
}
/**
@@ -167,13 +157,7 @@ public class ConnectPlusVoiceChannelUser extends CSBKMessage implements ITimeslo
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(timeslotFrequency.getNumber() == getChannel().getLogicalSlotNumber())
{
getChannel().setTimeslotFrequency(timeslotFrequency);
}
}
getChannel().apply(timeslotFrequencies);
}
@Override

View File

@@ -25,7 +25,6 @@ import io.github.dsheirer.identifier.integer.IntegerIdentifier;
import io.github.dsheirer.identifier.radio.RadioIdentifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
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;
@@ -224,11 +223,11 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
public int[] getLogicalChannelNumbers()
{
if(getMoveToChannel() instanceof DMRLogicalChannel)
if(getMoveToChannel() != null)
{
return ((DMRLogicalChannel)getMoveToChannel()).getLSNArray();
return getMoveToChannel().getLogicalChannelNumbers();
}
return new int[0];
@@ -241,17 +240,9 @@ public class Clear extends CSBKMessage implements ITimeslotFrequencyReceiver
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getMoveToChannel() instanceof DMRLogicalChannel)
if(getMoveToChannel() != null)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getMoveToChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
}
getMoveToChannel().apply(timeslotFrequencies);
}
}

View File

@@ -88,7 +88,8 @@ public class MoveTSCC extends CSBKMessage
if(multiBlock != null)
{
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
//Timeslot hard-coded to 1 since this is a control channel.
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,24 +23,20 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
import io.github.dsheirer.module.decode.dmr.channel.DMRChannel;
import io.github.dsheirer.module.decode.dmr.channel.DMRLogicalChannel;
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.message.CACH;
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
import java.util.ArrayList;
import java.util.List;
/**
* DMR Tier III - Announcement Message - Adjacent/Neighbor Site Information
*/
public class AdjacentSiteInformation extends Announcement implements ITimeslotFrequencyReceiver
public class AdjacentSiteInformation extends Announcement
{
private static final int NEIGHBOR_SYSTEM_IDENTITY_CODE_OFFSET = 21;
private static final int NETWORK_CONNECTION_STATUS_AVAILABLE_FLAG = 56;
@@ -88,8 +84,8 @@ public class AdjacentSiteInformation extends Announcement implements ITimeslotFr
if(multiBlock != null)
{
//Timeslot hard-coded to zero for control channel
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
//Timeslot hard-coded to one for control channel
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
}
}
@@ -235,39 +231,4 @@ public class AdjacentSiteInformation extends Announcement implements ITimeslotFr
return mIdentifiers;
}
/**
* Logical Slot Number(s) for channels contained in this message
*/
@Override
public int[] getLogicalTimeslotNumbers()
{
if(getNeighborChannel() instanceof DMRLogicalChannel)
{
return ((DMRLogicalChannel)getNeighborChannel()).getLSNArray();
}
return new int[0];
}
/**
* Applies the timeslot frequency lookup information to channels contained in this message
* @param timeslotFrequencies that match the logical timeslots
*/
@Override
public void apply(List<TimeslotFrequency> timeslotFrequencies)
{
if(getNeighborChannel() instanceof DMRLogicalChannel)
{
DMRLogicalChannel channel = (DMRLogicalChannel)getNeighborChannel();
for(TimeslotFrequency timeslotFrequency: timeslotFrequencies)
{
if(channel.getLogicalSlotNumber() == timeslotFrequency.getNumber())
{
channel.setTimeslotFrequency(timeslotFrequency);
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
* Copyright (C) 2014-2023 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,7 +27,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
import io.github.dsheirer.module.decode.dmr.message.type.AbsoluteChannelParameters;
import io.github.dsheirer.module.decode.dmr.message.type.DataType;
import java.util.ArrayList;
import java.util.List;
@@ -56,7 +55,8 @@ public class AnnounceChannelFrequency extends Announcement
if(multiBlock != null)
{
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 0);
//Timeslot hard-coded to one
mAbsoluteChannelParameters = new AbsoluteChannelParameters(multiBlock.getMessage(), 0, 1);
}
}

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