mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
Compare commits
138 Commits
v0.6.0-alp
...
v0.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0492f19a95 | ||
|
|
098b039154 | ||
|
|
5c6e6059f8 | ||
|
|
72d45543df | ||
|
|
e96fc46315 | ||
|
|
f0cfbb80dd | ||
|
|
a199f03a60 | ||
|
|
526c9609ef | ||
|
|
ec9ceeefb3 | ||
|
|
b6c6505824 | ||
|
|
93c61c48f9 | ||
|
|
9b89e80bb5 | ||
|
|
e0ecaa6e08 | ||
|
|
a7c89b5d1a | ||
|
|
834846d339 | ||
|
|
45c8bb73fa | ||
|
|
d2580f8a99 | ||
|
|
804c2e6359 | ||
|
|
3f2ec612d5 | ||
|
|
8bead998f5 | ||
|
|
2ecdaba183 | ||
|
|
82cb10a03d | ||
|
|
8717ce38eb | ||
|
|
5858a85a50 | ||
|
|
65d16da997 | ||
|
|
b1b5ae5fd2 | ||
|
|
b5ec6d8877 | ||
|
|
8599aebed9 | ||
|
|
2b9e753033 | ||
|
|
dba50744a9 | ||
|
|
490569e023 | ||
|
|
e79f2af759 | ||
|
|
ec7ee1ce91 | ||
|
|
672207c27c | ||
|
|
a1a6b3e988 | ||
|
|
2347135614 | ||
|
|
93438ddd97 | ||
|
|
ae31c41643 | ||
|
|
16aed8741d | ||
|
|
e878ca0c7f | ||
|
|
ca61dd2543 | ||
|
|
6187253589 | ||
|
|
b51c80742f | ||
|
|
15352b9216 | ||
|
|
a848b21ae8 | ||
|
|
279aca7287 | ||
|
|
88879c956a | ||
|
|
729b7e27a8 | ||
|
|
feaed168ae | ||
|
|
50edb18db3 | ||
|
|
7302e2806d | ||
|
|
8845306b4a | ||
|
|
09e19e1486 | ||
|
|
0f5478925c | ||
|
|
09dce0b0d1 | ||
|
|
ad98b1cb5e | ||
|
|
8effd97749 | ||
|
|
9dbed72c32 | ||
|
|
b4dd3692b8 | ||
|
|
030cbb856c | ||
|
|
511101e9b7 | ||
|
|
60dd2b2df7 | ||
|
|
218dc94e02 | ||
|
|
2440f578b1 | ||
|
|
09c87237cb | ||
|
|
3d974ddd91 | ||
|
|
1489c970ba | ||
|
|
9e0afca3d1 | ||
|
|
99eda4013e | ||
|
|
a62f81f890 | ||
|
|
66f0731731 | ||
|
|
dc239801c6 | ||
|
|
d4ba0988bc | ||
|
|
ad0f953b2a | ||
|
|
e1bc9eabd6 | ||
|
|
d3dc1a8667 | ||
|
|
bf278120de | ||
|
|
2f52f6e009 | ||
|
|
02363c3113 | ||
|
|
cfaac0fcbc | ||
|
|
bee0405e00 | ||
|
|
50bdf0ee81 | ||
|
|
a27ddbe0d0 | ||
|
|
0d652b9153 | ||
|
|
9468fd189b | ||
|
|
215737ad9b | ||
|
|
123c25b142 | ||
|
|
af56198f0e | ||
|
|
3c73fdfd6e | ||
|
|
b391ebbb5f | ||
|
|
bad5a1f9f0 | ||
|
|
a7470b463f | ||
|
|
d6c706cb35 | ||
|
|
a0d6950c75 | ||
|
|
2495200b36 | ||
|
|
bfe7ae0b1b | ||
|
|
4912abbf07 | ||
|
|
2f2998d02e | ||
|
|
c96349fa1c | ||
|
|
f640576cc3 | ||
|
|
77d725a488 | ||
|
|
2c0305f928 | ||
|
|
21fd61d507 | ||
|
|
8fb56a5342 | ||
|
|
5d7b73f876 | ||
|
|
85b2a38058 | ||
|
|
c78517ee75 | ||
|
|
ae116d3dfe | ||
|
|
93252f9a40 | ||
|
|
3c03a4db2e | ||
|
|
d0fcd617aa | ||
|
|
6ca0decbd6 | ||
|
|
ddd9ca933e | ||
|
|
b2cc29569e | ||
|
|
411735772b | ||
|
|
2ffb2963af | ||
|
|
227c88c8a2 | ||
|
|
cbe9c79498 | ||
|
|
1b8c100b43 | ||
|
|
30c5fd64f2 | ||
|
|
3a6b486340 | ||
|
|
ded7335dbf | ||
|
|
005a4f8119 | ||
|
|
1ac49bfa7f | ||
|
|
c302c4d121 | ||
|
|
b0a524cf62 | ||
|
|
d246ffff30 | ||
|
|
adfc8b2d6f | ||
|
|
ad2ab08466 | ||
|
|
f3369410b2 | ||
|
|
bac38a5bd8 | ||
|
|
e5f5df74ad | ||
|
|
b3837f581a | ||
|
|
b2ab6f58e9 | ||
|
|
e04b59ba15 | ||
|
|
366eacd234 | ||
|
|
f792876c0a | ||
|
|
19f7a5171b |
10
.github/workflows/gradle.yml
vendored
10
.github/workflows/gradle.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 19
|
||||
uses: actions/setup-java@v2
|
||||
- uses: actions/checkout@v4
|
||||
- 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
|
||||
|
||||
47
.github/workflows/nightly.yml
vendored
Normal file
47
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# Create nightly release for Linux, Mac and Windows using the Linux runner.
|
||||
name: Nightly Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
# Don't run nightly build for pull requests - uncomment to test changes to this action within scope of the PR review
|
||||
# pull_request:
|
||||
# branches: [ master ]
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 20
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '20'
|
||||
distribution: 'liberica'
|
||||
java-package: 'jdk+fx'
|
||||
|
||||
- name: Build Linux and Mac releases with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: runtimeZipOthers -PprojectVersion=nightly
|
||||
|
||||
- name: Build Windows release with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: runtimeZipWindows -PprojectVersion=nightly
|
||||
|
||||
- name: Create Build Info file
|
||||
run: |
|
||||
echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" > build/image/build_info.txt
|
||||
|
||||
- name: Add build artifacts to nightly release
|
||||
uses: pyTooling/Actions/releaser@main
|
||||
with:
|
||||
tag: nightly
|
||||
rm: false
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: |
|
||||
build/image/*.zip
|
||||
build/image/build_info.txt
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_19_PREVIEW" project-jdk-name="19" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_20_PREVIEW" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,8 +1,10 @@
|
||||

|
||||

|
||||
|
||||
# sdrtrunk
|
||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||
|
||||
* [Help/Wiki Home Page](https://github.com/DSheirer/sdrtrunk/wiki)
|
||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||
@@ -18,6 +20,13 @@ All release versions of sdrtrunk are available from the [releases](https://githu
|
||||
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
|
||||
* **(final)** These versions have been tested and are the current release version.
|
||||
|
||||
## Download Nightly Software Build
|
||||
The [nightly](https://github.com/DSheirer/sdrtrunk/releases/tag/nightly) release contains current builds of the software
|
||||
for all supported operating systems. This version of the software may contain bugs and may not run correctly. However,
|
||||
it let's you preview the most recent changes and fixes before the next software release. **Always backup your
|
||||
playlist(s) before you use the nightly builds.** Note: the nightly release is updated each time code changes are
|
||||
committed to the code base, so it's not really 'nightly' as much as it is 'current'.
|
||||
|
||||
## Minimum System Requirements
|
||||
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
||||
* **CPU:** 4-core
|
||||
|
||||
230
artifacts/sdrplay-api-headers/v3_08/sdrplay_api.h
Normal file
230
artifacts/sdrplay-api-headers/v3_08/sdrplay_api.h
Normal file
@@ -0,0 +1,230 @@
|
||||
#ifndef SDRPLAY_API_H
|
||||
#define SDRPLAY_API_H
|
||||
|
||||
#include "sdrplay_api_dev.h"
|
||||
#include "sdrplay_api_rx_channel.h"
|
||||
#include "sdrplay_api_callback.h"
|
||||
|
||||
//#if defined(_M_X64) || defined(_M_IX86)
|
||||
//#include "windows.h"
|
||||
//#elif defined (__GNUC__)
|
||||
typedef void *HANDLE;
|
||||
//#endif
|
||||
//
|
||||
//#ifndef _SDRPLAY_DLL_QUALIFIER
|
||||
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
|
||||
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
|
||||
//#elif defined(STATIC_LIB) || defined(__GNUC__)
|
||||
//#define _SDRPLAY_DLL_QUALIFIER
|
||||
//#endif
|
||||
//#endif // _SDRPLAY_DLL_QUALIFIER
|
||||
|
||||
// Application code should check that it is compiled against the same API version
|
||||
// sdrplay_api_ApiVersion() returns the API version
|
||||
#define SDRPLAY_API_VERSION (float)(3.08)
|
||||
|
||||
// API Constants
|
||||
#define SDRPLAY_MAX_DEVICES (16)
|
||||
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
|
||||
|
||||
#define SDRPLAY_MAX_SER_NO_LEN (64)
|
||||
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
|
||||
|
||||
#define SDRPLAY_RSP1_ID (1)
|
||||
#define SDRPLAY_RSP1A_ID (255)
|
||||
#define SDRPLAY_RSP2_ID (2)
|
||||
#define SDRPLAY_RSPduo_ID (3)
|
||||
#define SDRPLAY_RSPdx_ID (4)
|
||||
|
||||
// Enum types
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Success = 0,
|
||||
sdrplay_api_Fail = 1,
|
||||
sdrplay_api_InvalidParam = 2,
|
||||
sdrplay_api_OutOfRange = 3,
|
||||
sdrplay_api_GainUpdateError = 4,
|
||||
sdrplay_api_RfUpdateError = 5,
|
||||
sdrplay_api_FsUpdateError = 6,
|
||||
sdrplay_api_HwError = 7,
|
||||
sdrplay_api_AliasingError = 8,
|
||||
sdrplay_api_AlreadyInitialised = 9,
|
||||
sdrplay_api_NotInitialised = 10,
|
||||
sdrplay_api_NotEnabled = 11,
|
||||
sdrplay_api_HwVerError = 12,
|
||||
sdrplay_api_OutOfMemError = 13,
|
||||
sdrplay_api_ServiceNotResponding = 14,
|
||||
sdrplay_api_StartPending = 15,
|
||||
sdrplay_api_StopPending = 16,
|
||||
sdrplay_api_InvalidMode = 17,
|
||||
sdrplay_api_FailedVerification1 = 18,
|
||||
sdrplay_api_FailedVerification2 = 19,
|
||||
sdrplay_api_FailedVerification3 = 20,
|
||||
sdrplay_api_FailedVerification4 = 21,
|
||||
sdrplay_api_FailedVerification5 = 22,
|
||||
sdrplay_api_FailedVerification6 = 23,
|
||||
sdrplay_api_InvalidServiceVersion = 24
|
||||
} sdrplay_api_ErrT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Update_None = 0x00000000,
|
||||
|
||||
// Reasons for master only mode
|
||||
sdrplay_api_Update_Dev_Fs = 0x00000001,
|
||||
sdrplay_api_Update_Dev_Ppm = 0x00000002,
|
||||
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
|
||||
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
|
||||
|
||||
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
|
||||
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
|
||||
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
|
||||
|
||||
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
|
||||
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
|
||||
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
|
||||
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
|
||||
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
|
||||
|
||||
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
|
||||
|
||||
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
|
||||
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
|
||||
|
||||
// Reasons for master and slave mode
|
||||
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
|
||||
sdrplay_api_Update_Tuner_Gr = 0x00008000,
|
||||
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
|
||||
sdrplay_api_Update_Tuner_Frf = 0x00020000,
|
||||
sdrplay_api_Update_Tuner_BwType = 0x00040000,
|
||||
sdrplay_api_Update_Tuner_IfType = 0x00080000,
|
||||
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
|
||||
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
|
||||
|
||||
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
|
||||
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
|
||||
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
|
||||
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
|
||||
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
|
||||
|
||||
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
|
||||
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
|
||||
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
|
||||
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
|
||||
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
|
||||
} sdrplay_api_ReasonForUpdateT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Update_Ext1_None = 0x00000000,
|
||||
|
||||
// Reasons for master only mode
|
||||
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
|
||||
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
|
||||
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
|
||||
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
|
||||
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
|
||||
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
|
||||
|
||||
// Reasons for master and slave mode
|
||||
} sdrplay_api_ReasonForUpdateExtension1T;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_DbgLvl_Disable = 0,
|
||||
sdrplay_api_DbgLvl_Verbose = 1,
|
||||
sdrplay_api_DbgLvl_Warning = 2,
|
||||
sdrplay_api_DbgLvl_Error = 3,
|
||||
sdrplay_api_DbgLvl_Message = 4,
|
||||
} sdrplay_api_DbgLvl_t;
|
||||
|
||||
// Device structure
|
||||
typedef struct
|
||||
{
|
||||
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
|
||||
unsigned char hwVer;
|
||||
sdrplay_api_TunerSelectT tuner;
|
||||
sdrplay_api_RspDuoModeT rspDuoMode;
|
||||
unsigned char valid;
|
||||
double rspDuoSampleFreq;
|
||||
HANDLE dev;
|
||||
} sdrplay_api_DeviceT;
|
||||
|
||||
// Device parameter structure
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_DevParamsT *devParams;
|
||||
sdrplay_api_RxChannelParamsT *rxChannelA;
|
||||
sdrplay_api_RxChannelParamsT *rxChannelB;
|
||||
} sdrplay_api_DeviceParamsT;
|
||||
|
||||
// Extended error message structure
|
||||
typedef struct
|
||||
{
|
||||
char file[256];
|
||||
char function[256];
|
||||
int line;
|
||||
char message[1024];
|
||||
} sdrplay_api_ErrorInfoT;
|
||||
|
||||
// Comman API function types
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
|
||||
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
|
||||
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
|
||||
|
||||
// Device API function types
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate, double newSampleRate);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
|
||||
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
|
||||
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||
|
||||
// API function definitions
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// // Comman API function definitions
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Open(void);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Close(void);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
|
||||
// _SDRPLAY_DLL_QUALIFIER const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
|
||||
//
|
||||
// // Device API function definitions
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
|
||||
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
|
||||
// sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
|
||||
// sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //SDRPLAY_API_H
|
||||
78
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_callback.h
Normal file
78
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_callback.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef SDRPLAY_API_CALLBACK_H
|
||||
#define SDRPLAY_API_CALLBACK_H
|
||||
|
||||
// Event callback enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Overload_Detected = 0,
|
||||
sdrplay_api_Overload_Corrected = 1,
|
||||
} sdrplay_api_PowerOverloadCbEventIdT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_MasterInitialised = 0,
|
||||
sdrplay_api_SlaveAttached = 1,
|
||||
sdrplay_api_SlaveDetached = 2,
|
||||
sdrplay_api_SlaveInitialised = 3,
|
||||
sdrplay_api_SlaveUninitialised = 4,
|
||||
sdrplay_api_MasterDllDisappeared = 5,
|
||||
sdrplay_api_SlaveDllDisappeared = 6,
|
||||
} sdrplay_api_RspDuoModeCbEventIdT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_GainChange = 0,
|
||||
sdrplay_api_PowerOverloadChange = 1,
|
||||
sdrplay_api_DeviceRemoved = 2,
|
||||
sdrplay_api_RspDuoModeChange = 3,
|
||||
} sdrplay_api_EventT;
|
||||
|
||||
// Event callback parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned int gRdB;
|
||||
unsigned int lnaGRdB;
|
||||
double currGain;
|
||||
} sdrplay_api_GainCbParamT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
|
||||
} sdrplay_api_PowerOverloadCbParamT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
|
||||
} sdrplay_api_RspDuoModeCbParamT;
|
||||
|
||||
// Event parameters overlay
|
||||
typedef union
|
||||
{
|
||||
sdrplay_api_GainCbParamT gainParams;
|
||||
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
|
||||
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
|
||||
} sdrplay_api_EventParamsT;
|
||||
|
||||
// Stream callback parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned int firstSampleNum;
|
||||
int grChanged;
|
||||
int rfChanged;
|
||||
int fsChanged;
|
||||
unsigned int numSamples;
|
||||
} sdrplay_api_StreamCbParamsT;
|
||||
|
||||
// Callback function prototypes
|
||||
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
|
||||
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
|
||||
|
||||
// Callback function struct
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_StreamCallback_t StreamACbFn;
|
||||
sdrplay_api_StreamCallback_t StreamBCbFn;
|
||||
sdrplay_api_EventCallback_t EventCbFn;
|
||||
} sdrplay_api_CallbackFnsT;
|
||||
|
||||
#endif //SDRPLAY_API_CALLBACK_H
|
||||
55
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_control.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef SDRPLAY_API_CONTROL_H
|
||||
#define SDRPLAY_API_CONTROL_H
|
||||
|
||||
// Control parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_AGC_DISABLE = 0,
|
||||
sdrplay_api_AGC_100HZ = 1,
|
||||
sdrplay_api_AGC_50HZ = 2,
|
||||
sdrplay_api_AGC_5HZ = 3,
|
||||
sdrplay_api_AGC_CTRL_EN = 4
|
||||
} sdrplay_api_AgcControlT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_ADSB_DECIMATION = 0,
|
||||
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
|
||||
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
|
||||
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
|
||||
} sdrplay_api_AdsbModeT;
|
||||
|
||||
// Control parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char DCenable; // default: 1
|
||||
unsigned char IQenable; // default: 1
|
||||
} sdrplay_api_DcOffsetT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char enable; // default: 0
|
||||
unsigned char decimationFactor; // default: 1
|
||||
unsigned char wideBandSignal; // default: 0
|
||||
} sdrplay_api_DecimationT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
|
||||
int setPoint_dBfs; // default: -60
|
||||
unsigned short attack_ms; // default: 0
|
||||
unsigned short decay_ms; // default: 0
|
||||
unsigned short decay_delay_ms; // default: 0
|
||||
unsigned short decay_threshold_dB; // default: 0
|
||||
int syncUpdate; // default: 0
|
||||
} sdrplay_api_AgcT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_DcOffsetT dcOffset;
|
||||
sdrplay_api_DecimationT decimation;
|
||||
sdrplay_api_AgcT agc;
|
||||
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
|
||||
} sdrplay_api_ControlParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_CONTROL_H
|
||||
51
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_dev.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef SDRPLAY_API_DEV_H
|
||||
#define SDRPLAY_API_DEV_H
|
||||
|
||||
#include "sdrplay_api_rsp1a.h"
|
||||
#include "sdrplay_api_rsp2.h"
|
||||
#include "sdrplay_api_rspDuo.h"
|
||||
#include "sdrplay_api_rspDx.h"
|
||||
|
||||
// Dev parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_ISOCH = 0,
|
||||
sdrplay_api_BULK = 1
|
||||
} sdrplay_api_TransferModeT;
|
||||
|
||||
// Dev parameter structs
|
||||
typedef struct
|
||||
{
|
||||
double fsHz; // default: 2000000.0
|
||||
unsigned char syncUpdate; // default: 0
|
||||
unsigned char reCal; // default: 0
|
||||
} sdrplay_api_FsFreqT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int sampleNum; // default: 0
|
||||
unsigned int period; // default: 0
|
||||
} sdrplay_api_SyncUpdateT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char resetGainUpdate; // default: 0
|
||||
unsigned char resetRfUpdate; // default: 0
|
||||
unsigned char resetFsUpdate; // default: 0
|
||||
} sdrplay_api_ResetFlagsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double ppm; // default: 0.0
|
||||
sdrplay_api_FsFreqT fsFreq;
|
||||
sdrplay_api_SyncUpdateT syncUpdate;
|
||||
sdrplay_api_ResetFlagsT resetFlags;
|
||||
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
|
||||
unsigned int samplesPerPkt; // default: 0 (output param)
|
||||
sdrplay_api_Rsp1aParamsT rsp1aParams;
|
||||
sdrplay_api_Rsp2ParamsT rsp2Params;
|
||||
sdrplay_api_RspDuoParamsT rspDuoParams;
|
||||
sdrplay_api_RspDxParamsT rspDxParams;
|
||||
} sdrplay_api_DevParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_DEV_H
|
||||
22
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp1a.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef SDRPLAY_API_RSP1A_H
|
||||
#define SDRPLAY_API_RSP1A_H
|
||||
|
||||
#define RSPIA_NUM_LNA_STATES 10
|
||||
#define RSPIA_NUM_LNA_STATES_AM 7
|
||||
#define RSPIA_NUM_LNA_STATES_LBAND 9
|
||||
|
||||
// RSP1A parameter enums
|
||||
|
||||
// RSP1A parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char rfNotchEnable; // default: 0
|
||||
unsigned char rfDabNotchEnable; // default: 0
|
||||
} sdrplay_api_Rsp1aParamsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char biasTEnable; // default: 0
|
||||
} sdrplay_api_Rsp1aTunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSP1A_H
|
||||
35
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rsp2.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef SDRPLAY_API_RSP2_H
|
||||
#define SDRPLAY_API_RSP2_H
|
||||
|
||||
#define RSPII_NUM_LNA_STATES 9
|
||||
#define RSPII_NUM_LNA_STATES_AMPORT 5
|
||||
#define RSPII_NUM_LNA_STATES_420MHZ 6
|
||||
|
||||
// RSP2 parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Rsp2_ANTENNA_A = 5,
|
||||
sdrplay_api_Rsp2_ANTENNA_B = 6,
|
||||
} sdrplay_api_Rsp2_AntennaSelectT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Rsp2_AMPORT_1 = 1,
|
||||
sdrplay_api_Rsp2_AMPORT_2 = 0,
|
||||
} sdrplay_api_Rsp2_AmPortSelectT;
|
||||
|
||||
// RSP2 parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char extRefOutputEn; // default: 0
|
||||
} sdrplay_api_Rsp2ParamsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char biasTEnable; // default: 0
|
||||
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
|
||||
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
|
||||
unsigned char rfNotchEnable; // default: 0
|
||||
} sdrplay_api_Rsp2TunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSP2_H
|
||||
40
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDuo.h
Normal file
40
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDuo.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef SDRPLAY_API_RSPduo_H
|
||||
#define SDRPLAY_API_RSPduo_H
|
||||
|
||||
#define RSPDUO_NUM_LNA_STATES 10
|
||||
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
|
||||
#define RSPDUO_NUM_LNA_STATES_AM 7
|
||||
#define RSPDUO_NUM_LNA_STATES_LBAND 9
|
||||
|
||||
// RSPduo parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_RspDuoMode_Unknown = 0,
|
||||
sdrplay_api_RspDuoMode_Single_Tuner = 1,
|
||||
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
|
||||
sdrplay_api_RspDuoMode_Master = 4,
|
||||
sdrplay_api_RspDuoMode_Slave = 8,
|
||||
} sdrplay_api_RspDuoModeT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_RspDuo_AMPORT_1 = 1,
|
||||
sdrplay_api_RspDuo_AMPORT_2 = 0,
|
||||
} sdrplay_api_RspDuo_AmPortSelectT;
|
||||
|
||||
// RSPduo parameter structs
|
||||
typedef struct
|
||||
{
|
||||
int extRefOutputEn; // default: 0
|
||||
} sdrplay_api_RspDuoParamsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char biasTEnable; // default: 0
|
||||
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
|
||||
unsigned char tuner1AmNotchEnable; // default: 0
|
||||
unsigned char rfNotchEnable; // default: 0
|
||||
unsigned char rfDabNotchEnable; // default: 0
|
||||
} sdrplay_api_RspDuoTunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSPduo_H
|
||||
45
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDx.h
Normal file
45
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rspDx.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef SDRPLAY_API_RSPDX_H
|
||||
#define SDRPLAY_API_RSPDX_H
|
||||
|
||||
#include "sdrplay_api_tuner.h"
|
||||
|
||||
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
|
||||
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
|
||||
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_60 20 // Number of LNA states when using AM Port 2 between 12 and 60MHz
|
||||
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
|
||||
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
|
||||
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
|
||||
#define RSPDX_NUM_LNA_STATES_DX 26 // Number of LNA states in DX path
|
||||
|
||||
// RSPdx parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_RspDx_ANTENNA_A = 0,
|
||||
sdrplay_api_RspDx_ANTENNA_B = 1,
|
||||
sdrplay_api_RspDx_ANTENNA_C = 2,
|
||||
} sdrplay_api_RspDx_AntennaSelectT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
|
||||
} sdrplay_api_RspDx_HdrModeBwT;
|
||||
|
||||
// RSPdx parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char hdrEnable; // default: 0
|
||||
unsigned char biasTEnable; // default: 0
|
||||
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
|
||||
unsigned char rfNotchEnable; // default: 0
|
||||
unsigned char rfDabNotchEnable; // default: 0
|
||||
} sdrplay_api_RspDxParamsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
|
||||
} sdrplay_api_RspDxTunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSPDX_H
|
||||
21
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_rx_channel.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef SDRPLAY_API_RX_CHANNEL_H
|
||||
#define SDRPLAY_API_RX_CHANNEL_H
|
||||
|
||||
#include "sdrplay_api_tuner.h"
|
||||
#include "sdrplay_api_control.h"
|
||||
#include "sdrplay_api_rsp1a.h"
|
||||
#include "sdrplay_api_rsp2.h"
|
||||
#include "sdrplay_api_rspDuo.h"
|
||||
#include "sdrplay_api_rspDx.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_TunerParamsT tunerParams;
|
||||
sdrplay_api_ControlParamsT ctrlParams;
|
||||
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
|
||||
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
|
||||
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
|
||||
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
|
||||
} sdrplay_api_RxChannelParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RX_CHANNEL_H
|
||||
93
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_08/sdrplay_api_tuner.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef SDRPLAY_API_TUNER_H
|
||||
#define SDRPLAY_API_TUNER_H
|
||||
|
||||
#define MAX_BB_GR (59)
|
||||
|
||||
// Tuner parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_BW_Undefined = 0,
|
||||
sdrplay_api_BW_0_200 = 200,
|
||||
sdrplay_api_BW_0_300 = 300,
|
||||
sdrplay_api_BW_0_600 = 600,
|
||||
sdrplay_api_BW_1_536 = 1536,
|
||||
sdrplay_api_BW_5_000 = 5000,
|
||||
sdrplay_api_BW_6_000 = 6000,
|
||||
sdrplay_api_BW_7_000 = 7000,
|
||||
sdrplay_api_BW_8_000 = 8000
|
||||
} sdrplay_api_Bw_MHzT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_IF_Undefined = -1,
|
||||
sdrplay_api_IF_Zero = 0,
|
||||
sdrplay_api_IF_0_450 = 450,
|
||||
sdrplay_api_IF_1_620 = 1620,
|
||||
sdrplay_api_IF_2_048 = 2048
|
||||
} sdrplay_api_If_kHzT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_LO_Undefined = 0,
|
||||
sdrplay_api_LO_Auto = 1,
|
||||
sdrplay_api_LO_120MHz = 2,
|
||||
sdrplay_api_LO_144MHz = 3,
|
||||
sdrplay_api_LO_168MHz = 4
|
||||
} sdrplay_api_LoModeT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_EXTENDED_MIN_GR = 0,
|
||||
sdrplay_api_NORMAL_MIN_GR = 20
|
||||
} sdrplay_api_MinGainReductionT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Tuner_Neither = 0,
|
||||
sdrplay_api_Tuner_A = 1,
|
||||
sdrplay_api_Tuner_B = 2,
|
||||
sdrplay_api_Tuner_Both = 3,
|
||||
} sdrplay_api_TunerSelectT;
|
||||
|
||||
// Tuner parameter structs
|
||||
typedef struct
|
||||
{
|
||||
float curr;
|
||||
float max;
|
||||
float min;
|
||||
} sdrplay_api_GainValuesT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int gRdB; // default: 50
|
||||
unsigned char LNAstate; // default: 0
|
||||
unsigned char syncUpdate; // default: 0
|
||||
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
|
||||
sdrplay_api_GainValuesT gainVals; // output parameter
|
||||
} sdrplay_api_GainT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double rfHz; // default: 200000000.0
|
||||
unsigned char syncUpdate; // default: 0
|
||||
} sdrplay_api_RfFreqT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned char dcCal; // default: 3 (Periodic mode)
|
||||
unsigned char speedUp; // default: 0 (No speedup)
|
||||
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
|
||||
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
|
||||
} sdrplay_api_DcOffsetTunerT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
|
||||
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
|
||||
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
|
||||
sdrplay_api_GainT gain;
|
||||
sdrplay_api_RfFreqT rfFreq;
|
||||
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
|
||||
} sdrplay_api_TunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_TUNER_H
|
||||
29
build.gradle
29
build.gradle
@@ -53,12 +53,13 @@ repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
version = '0.6.0-alpha6'
|
||||
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
|
||||
version = project.findProperty('projectVersion')
|
||||
|
||||
//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,21 +198,21 @@ 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
|
||||
def jdk_download_osx_x86_64 = jdk_download_base + "macos-aarch64" + jdk_download_suffix
|
||||
def jdk_download_osx_aarch64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
|
||||
def jdk_download_osx_x86_64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
|
||||
def jdk_download_osx_aarch64 = jdk_download_base + "macos-aarch64" + jdk_download_suffix
|
||||
def jdk_download_windows_x86_64 = jdk_download_base + "windows-amd64-full.zip"
|
||||
|
||||
/**
|
||||
@@ -228,7 +229,8 @@ def configure(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
|
||||
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
|
||||
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
|
||||
//jdk.accessibility is used with assistive technologies like screen readers
|
||||
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility')
|
||||
//java.management for JVM resource monitoring
|
||||
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility', 'java.management')
|
||||
|
||||
//Use auto-detected modules and 'add' any specified modules.
|
||||
rt.extension.additive.set(true)
|
||||
@@ -339,7 +341,7 @@ tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||
jdkHome = jdkDownload(jdk_download_osx_aarch64)
|
||||
}
|
||||
}
|
||||
//Linux x86-64
|
||||
//OSX x86-64
|
||||
if(file(jdk_osx_x86_64).exists()) {
|
||||
rt.extension.targetPlatform(targetOsxX86_64, jdk_osx_x86_64, [])
|
||||
}
|
||||
@@ -351,4 +353,3 @@ tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||
|
||||
configure(rt, jvmArgsLinux);
|
||||
}
|
||||
|
||||
|
||||
31
gradle.properties
Normal file
31
gradle.properties
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# ******************************************************************************
|
||||
# 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/>
|
||||
# *****************************************************************************
|
||||
#
|
||||
|
||||
# Version naming conventions:
|
||||
# Alpha: maj.min.patch-alpha-build
|
||||
# Beta: maj.min.patch-beta-build
|
||||
# Release: maj.min.patch
|
||||
#
|
||||
# Example: 0.6.0-beta-5
|
||||
#
|
||||
# Note: this is so that the file names for the resultant release build products match the GitLab release asset
|
||||
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
||||
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
||||
|
||||
projectVersion=0.6.0
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# ******************************************************************************
|
||||
# Copyright (C) 2014-2022 Dennis Sheirer
|
||||
# Copyright (C) 2014-2023 Dennis Sheirer
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,37 +19,31 @@
|
||||
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.P25FullyQualifiedRadio;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
|
||||
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 +58,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();
|
||||
@@ -80,6 +79,18 @@ public class AliasFactory
|
||||
Min copyMin = new Min();
|
||||
copyMin.setMin(originalMin.getMin());
|
||||
return copyMin;
|
||||
case P25_FULLY_QUALIFIED_RADIO_ID:
|
||||
P25FullyQualifiedRadio originalP25 = (P25FullyQualifiedRadio) id;
|
||||
P25FullyQualifiedRadio copyP25 = new P25FullyQualifiedRadio(originalP25.getWacn(),
|
||||
originalP25.getSystem(), originalP25.getValue());
|
||||
copyP25.setOverlap(originalP25.overlapProperty().get());
|
||||
return copyP25;
|
||||
case P25_FULLY_QUALIFIED_TALKGROUP:
|
||||
P25FullyQualifiedTalkgroup originalFqt = (P25FullyQualifiedTalkgroup) id;
|
||||
P25FullyQualifiedTalkgroup copyFqt = new P25FullyQualifiedTalkgroup(originalFqt.getWacn(),
|
||||
originalFqt.getSystem(), originalFqt.getValue());
|
||||
copyFqt.setOverlap(originalFqt.overlapProperty().get());
|
||||
return copyFqt;
|
||||
case PRIORITY:
|
||||
Priority originalPriority = (Priority)id;
|
||||
Priority copyPriority = new Priority();
|
||||
@@ -187,6 +198,7 @@ public class AliasFactory
|
||||
copy.setGroup(original.getGroup());
|
||||
copy.setColor(original.getColor());
|
||||
copy.setIconName(original.getIconName());
|
||||
copy.setRecordable(original.isRecordable());
|
||||
return copy;
|
||||
}
|
||||
|
||||
@@ -234,65 +246,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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,32 +21,34 @@ 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.P25FullyQualifiedRadio;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
|
||||
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 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;
|
||||
import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.status.UnitStatusIdentifier;
|
||||
import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier;
|
||||
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 +59,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 +73,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<>();
|
||||
@@ -152,6 +159,32 @@ public class AliasList
|
||||
|
||||
talkgroupRangeAliasList.add(talkgroupRange, alias);
|
||||
break;
|
||||
case P25_FULLY_QUALIFIED_RADIO_ID:
|
||||
P25FullyQualifiedRadio qualifiedRadio = (P25FullyQualifiedRadio) id;
|
||||
|
||||
RadioAliasList p25RadioAliasList = mRadioProtocolMap.get(qualifiedRadio.getProtocol());
|
||||
|
||||
if(p25RadioAliasList == null)
|
||||
{
|
||||
p25RadioAliasList = new RadioAliasList();
|
||||
mRadioProtocolMap.put(qualifiedRadio.getProtocol(), p25RadioAliasList);
|
||||
}
|
||||
|
||||
p25RadioAliasList.add(qualifiedRadio, alias);
|
||||
break;
|
||||
case P25_FULLY_QUALIFIED_TALKGROUP:
|
||||
P25FullyQualifiedTalkgroup qualifiedTalkgroup = (P25FullyQualifiedTalkgroup) id;
|
||||
|
||||
TalkgroupAliasList p25TalkgroupAliasList = mTalkgroupProtocolMap.get(qualifiedTalkgroup.getProtocol());
|
||||
|
||||
if(p25TalkgroupAliasList == null)
|
||||
{
|
||||
p25TalkgroupAliasList = new TalkgroupAliasList();
|
||||
mTalkgroupProtocolMap.put(qualifiedTalkgroup.getProtocol(), p25TalkgroupAliasList);
|
||||
}
|
||||
|
||||
p25TalkgroupAliasList.add(qualifiedTalkgroup, alias);
|
||||
break;
|
||||
case RADIO_ID:
|
||||
Radio radio = (Radio)id;
|
||||
|
||||
@@ -178,6 +211,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();
|
||||
|
||||
@@ -379,6 +418,8 @@ public class AliasList
|
||||
}
|
||||
break;
|
||||
case PATCH_GROUP:
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
|
||||
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||
|
||||
@@ -386,8 +427,6 @@ public class AliasList
|
||||
|
||||
if(patchGroupAliasList != null)
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
|
||||
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
||||
|
||||
if(alias != null)
|
||||
@@ -395,19 +434,36 @@ public class AliasList
|
||||
aliases.add(alias);
|
||||
}
|
||||
|
||||
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers())
|
||||
for(TalkgroupIdentifier patchedTalkgroup: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||
{
|
||||
Alias patchedAlias = patchGroupAliasList.getAlias(patchedGroup);
|
||||
Alias patchedTalkgroupAlias = patchGroupAliasList.getAlias(patchedTalkgroup);
|
||||
|
||||
if(patchedAlias != null && !aliases.contains(patchedAlias))
|
||||
if(patchedTalkgroupAlias != null && !aliases.contains(patchedTalkgroupAlias))
|
||||
{
|
||||
aliases.add(patchedAlias);
|
||||
aliases.add(patchedTalkgroupAlias);
|
||||
}
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
break;
|
||||
|
||||
if(patchGroup.hasPatchedRadios())
|
||||
{
|
||||
RadioAliasList radioAliasList = mRadioProtocolMap.get(patchGroupIdentifier.getProtocol());
|
||||
|
||||
if(radioAliasList != null)
|
||||
{
|
||||
for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers())
|
||||
{
|
||||
Alias patchedRadioAlias = radioAliasList.getAlias(patchedRadio);
|
||||
|
||||
if(patchedRadioAlias != null && !aliases.contains(patchedRadioAlias))
|
||||
{
|
||||
aliases.add(patchedRadioAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aliases;
|
||||
case RADIO:
|
||||
RadioIdentifier radio = (RadioIdentifier)identifier;
|
||||
|
||||
@@ -439,9 +495,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 +510,15 @@ public class AliasList
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(identifier instanceof DCSIdentifier dcsIdentifier)
|
||||
{
|
||||
DCSCode dcsCode = dcsIdentifier.getValue();
|
||||
|
||||
if(dcsCode != null)
|
||||
{
|
||||
return toList(mDCSCodeAliasMap.get(dcsCode));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -465,9 +530,7 @@ public class AliasList
|
||||
{
|
||||
if(alias != null)
|
||||
{
|
||||
List<Alias> aliases = new ArrayList<>();
|
||||
aliases.add(alias);
|
||||
return aliases;
|
||||
return Collections.singletonList(alias);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
@@ -590,6 +653,7 @@ public class AliasList
|
||||
*/
|
||||
public class TalkgroupAliasList
|
||||
{
|
||||
private Map<String,Alias> mFullyQualifiedTalkgroupAliasMap = new HashMap<>();
|
||||
private Map<Integer,Alias> mTalkgroupAliasMap = new TreeMap<>();
|
||||
private Map<TalkgroupRange, Alias> mTalkgroupRangeAliasMap = new HashMap<>();
|
||||
|
||||
@@ -599,6 +663,11 @@ public class AliasList
|
||||
|
||||
public Alias getAlias(TalkgroupIdentifier identifier)
|
||||
{
|
||||
if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti)
|
||||
{
|
||||
return mFullyQualifiedTalkgroupAliasMap.get(fqti.toString());
|
||||
}
|
||||
|
||||
int value = identifier.getValue();
|
||||
|
||||
Alias mapValue = mTalkgroupAliasMap.get(value);
|
||||
@@ -620,26 +689,55 @@ public class AliasList
|
||||
|
||||
public void add(Talkgroup talkgroup, Alias alias)
|
||||
{
|
||||
//Detect talkgroup collisions and set overlap flag for both
|
||||
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
|
||||
if(talkgroup instanceof P25FullyQualifiedTalkgroup fqt)
|
||||
{
|
||||
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
|
||||
|
||||
if(!existing.equals(alias))
|
||||
//Detect collisions
|
||||
if(mFullyQualifiedTalkgroupAliasMap.containsKey(fqt.getHashKey()))
|
||||
{
|
||||
talkgroup.setOverlap(true);
|
||||
Alias existing = mFullyQualifiedTalkgroupAliasMap.get(fqt.getHashKey());
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
if(!existing.equals(alias))
|
||||
{
|
||||
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
|
||||
fqt.setOverlap(true);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
if(aliasID instanceof P25FullyQualifiedTalkgroup existingFqt &&
|
||||
existingFqt.getHashKey().contentEquals(fqt.getHashKey()))
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mFullyQualifiedTalkgroupAliasMap.put(fqt.getHashKey(), alias);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Detect talkgroup collisions and set overlap flag for both
|
||||
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()))
|
||||
{
|
||||
Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue());
|
||||
|
||||
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
|
||||
if(!existing.equals(alias))
|
||||
{
|
||||
talkgroup.setOverlap(true);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue())
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mTalkgroupAliasMap.put(talkgroup.getValue(), alias);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(TalkgroupRange talkgroupRange, Alias alias)
|
||||
@@ -672,6 +770,7 @@ public class AliasList
|
||||
*/
|
||||
public class RadioAliasList
|
||||
{
|
||||
private Map<String,Alias> mFullyQualifiedRadioAliasMap = new HashMap<>();
|
||||
private Map<Integer,Alias> mRadioAliasMap = new TreeMap<>();
|
||||
private Map<RadioRange, Alias> mRadioRangeAliasMap = new HashMap<>();
|
||||
|
||||
@@ -681,6 +780,11 @@ public class AliasList
|
||||
|
||||
public Alias getAlias(RadioIdentifier identifier)
|
||||
{
|
||||
if(identifier instanceof FullyQualifiedRadioIdentifier fqri)
|
||||
{
|
||||
return mFullyQualifiedRadioAliasMap.get(fqri.toString());
|
||||
}
|
||||
|
||||
int value = identifier.getValue();
|
||||
|
||||
Alias mapValue = mRadioAliasMap.get(value);
|
||||
@@ -702,26 +806,55 @@ public class AliasList
|
||||
|
||||
public void add(Radio radio, Alias alias)
|
||||
{
|
||||
//Detect collisions
|
||||
if(mRadioAliasMap.containsKey(radio.getValue()))
|
||||
if(radio instanceof P25FullyQualifiedRadio fqr)
|
||||
{
|
||||
Alias existing = mRadioAliasMap.get(radio.getValue());
|
||||
|
||||
if(!existing.equals(alias))
|
||||
//Detect collisions
|
||||
if(mFullyQualifiedRadioAliasMap.containsKey(fqr.getHashKey()))
|
||||
{
|
||||
radio.setOverlap(true);
|
||||
Alias existing = mFullyQualifiedRadioAliasMap.get(fqr.getHashKey());
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
if(!existing.equals(alias))
|
||||
{
|
||||
if(aliasID instanceof Radio && ((Radio)aliasID).getValue() == radio.getValue())
|
||||
fqr.setOverlap(true);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
if(aliasID instanceof P25FullyQualifiedRadio existingFqr &&
|
||||
existingFqr.getHashKey().contentEquals(fqr.getHashKey()))
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mFullyQualifiedRadioAliasMap.put(fqr.getHashKey(), alias);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Detect collisions
|
||||
if(mRadioAliasMap.containsKey(radio.getValue()))
|
||||
{
|
||||
Alias existing = mRadioAliasMap.get(radio.getValue());
|
||||
|
||||
mRadioAliasMap.put(radio.getValue(), alias);
|
||||
if(!existing.equals(alias))
|
||||
{
|
||||
radio.setOverlap(true);
|
||||
|
||||
for(AliasID aliasID: existing.getAliasIdentifiers())
|
||||
{
|
||||
if(aliasID instanceof Radio existingRadio && (existingRadio.getValue() == radio.getValue()))
|
||||
{
|
||||
aliasID.setOverlap(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mRadioAliasMap.put(radio.getValue(), alias);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(RadioRange radioRange, Alias alias)
|
||||
|
||||
@@ -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;
|
||||
@@ -37,11 +35,13 @@ 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.P25FullyQualifiedRadio;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.record.Record;
|
||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||
@@ -54,6 +54,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"),
|
||||
@@ -62,6 +63,8 @@ import javafx.util.Callback;
|
||||
@JsonSubTypes.Type(value = Min.class, name = "min"),
|
||||
@JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"),
|
||||
@JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"),
|
||||
@JsonSubTypes.Type(value = P25FullyQualifiedRadio.class, name = "p25FullyQualifiedRadio"),
|
||||
@JsonSubTypes.Type(value = P25FullyQualifiedTalkgroup.class, name = "p25FullyQualifiedTalkgroup"),
|
||||
@JsonSubTypes.Type(value = Priority.class, name = "priority"),
|
||||
@JsonSubTypes.Type(value = Radio.class, name = "radio"),
|
||||
@JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"),
|
||||
|
||||
@@ -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,11 +23,14 @@ import java.util.EnumSet;
|
||||
public enum AliasIDType
|
||||
{
|
||||
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
||||
DCS("Digital Coded Squelch (DCS)"),
|
||||
ESN("ESN"),
|
||||
INVERT("Audio Inversion"),
|
||||
LOJACK("LoJack"),
|
||||
LTR_NET_UID("LTR-Net UID"),
|
||||
MIN("Passport MIN"),
|
||||
P25_FULLY_QUALIFIED_RADIO_ID("P25 Fully Qualified Radio ID"),
|
||||
P25_FULLY_QUALIFIED_TALKGROUP("P25 Fully Qualified Talkgroup"),
|
||||
PRIORITY("Audio Priority"),
|
||||
RADIO_ID("Radio ID"),
|
||||
RADIO_ID_RANGE("Radio ID Range"),
|
||||
@@ -58,7 +58,7 @@ public enum AliasIDType
|
||||
|
||||
//Values used by the View-By alias editor
|
||||
public static EnumSet<AliasIDType> VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE,
|
||||
UNIT_STATUS, STATUS, TONES);
|
||||
P25_FULLY_QUALIFIED_RADIO_ID, P25_FULLY_QUALIFIED_TALKGROUP, UNIT_STATUS, STATUS, TONES);
|
||||
|
||||
public String toString()
|
||||
{
|
||||
|
||||
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.alias.id.dcs;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||
|
||||
/**
|
||||
* Digital Coded Squelch (DCS) tone identifier
|
||||
*/
|
||||
public class Dcs extends AliasID implements Comparable<Dcs>
|
||||
{
|
||||
private DCSCode mDCSCode;
|
||||
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.DCS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(AliasID id)
|
||||
{
|
||||
if(isValid() && id instanceof Dcs other)
|
||||
{
|
||||
return other.isValid() && getDCSCode().equals(other.getDCSCode());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* DCS code
|
||||
* @return DCS code or null.
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "code")
|
||||
public DCSCode getDCSCode()
|
||||
{
|
||||
return mDCSCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DCS code value.
|
||||
* @param dcsCode to set
|
||||
*/
|
||||
public void setDCSCode(DCSCode dcsCode)
|
||||
{
|
||||
mDCSCode = dcsCode;
|
||||
updateValueProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid()
|
||||
{
|
||||
return mDCSCode != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAudioIdentifier()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if(isValid())
|
||||
{
|
||||
return getDCSCode().toString();
|
||||
}
|
||||
|
||||
return "DCS-Invalid - No Tone Selected";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Dcs o)
|
||||
{
|
||||
if(isValid())
|
||||
{
|
||||
if(o.isValid())
|
||||
{
|
||||
return getDCSCode().compareTo(o.getDCSCode());
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.radio;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Fully qualified radio. Note: this is only for P25.
|
||||
*/
|
||||
public class P25FullyQualifiedRadio extends Radio
|
||||
{
|
||||
private int mWacn;
|
||||
private int mSystem;
|
||||
|
||||
public P25FullyQualifiedRadio()
|
||||
{
|
||||
//No-arg JAXB constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param wacn for the radio
|
||||
* @param system for the radio
|
||||
* @param id for the radio
|
||||
*/
|
||||
public P25FullyQualifiedRadio(int wacn, int system, int id)
|
||||
{
|
||||
super(Protocol.APCO25, id);
|
||||
mWacn = wacn;
|
||||
mSystem = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.P25_FULLY_QUALIFIED_RADIO_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* WACN for this radio
|
||||
* @return wacn
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "wacn")
|
||||
public int getWacn()
|
||||
{
|
||||
return mWacn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the WACN for this radio
|
||||
* @param wacn for this radio
|
||||
*/
|
||||
public void setWacn(int wacn)
|
||||
{
|
||||
mWacn = wacn;
|
||||
}
|
||||
|
||||
/**
|
||||
* System for this radio
|
||||
* @return system
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "system")
|
||||
public int getSystem()
|
||||
{
|
||||
return mSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the system for this radio
|
||||
* @param system for this radio
|
||||
*/
|
||||
public void setSystem(int system)
|
||||
{
|
||||
mSystem = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Fully Qualified Radio ID:").append(getWacn());
|
||||
sb.append(".").append(getSystem());
|
||||
sb.append(".").append(getValue());
|
||||
sb.append(" Protocol:").append((getProtocol()));
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" **NOT VALID**");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashable string key for use in a lookup map
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getHashKey()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getWacn());
|
||||
sb.append(".").append(getSystem());
|
||||
sb.append(".").append(getValue());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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.alias.id.talkgroup;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* Fully qualified talkgroup. Note: this is only for P25.
|
||||
*/
|
||||
public class P25FullyQualifiedTalkgroup extends Talkgroup
|
||||
{
|
||||
private int mWacn;
|
||||
private int mSystem;
|
||||
|
||||
public P25FullyQualifiedTalkgroup()
|
||||
{
|
||||
//No-arg JAXB constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param wacn for the radio
|
||||
* @param system for the radio
|
||||
* @param talkgroup value
|
||||
*/
|
||||
public P25FullyQualifiedTalkgroup(int wacn, int system, int talkgroup)
|
||||
{
|
||||
super(Protocol.APCO25, talkgroup);
|
||||
mWacn = wacn;
|
||||
mSystem = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AliasIDType getType()
|
||||
{
|
||||
return AliasIDType.P25_FULLY_QUALIFIED_TALKGROUP;
|
||||
}
|
||||
|
||||
/**
|
||||
* WACN for this radio
|
||||
* @return wacn
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "wacn")
|
||||
public int getWacn()
|
||||
{
|
||||
return mWacn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the WACN for this radio
|
||||
* @param wacn for this radio
|
||||
*/
|
||||
public void setWacn(int wacn)
|
||||
{
|
||||
mWacn = wacn;
|
||||
}
|
||||
|
||||
/**
|
||||
* System for this radio
|
||||
* @return system
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "system")
|
||||
public int getSystem()
|
||||
{
|
||||
return mSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the system for this radio
|
||||
* @param system for this radio
|
||||
*/
|
||||
public void setSystem(int system)
|
||||
{
|
||||
mSystem = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Fully Qualified Radio ID:").append(getWacn());
|
||||
sb.append(".").append(getSystem());
|
||||
sb.append(".").append(getValue());
|
||||
sb.append(" Protocol:").append((getProtocol()));
|
||||
|
||||
if(!isValid())
|
||||
{
|
||||
sb.append(" **NOT VALID**");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashable string key for use in a lookup map
|
||||
* @return
|
||||
*/
|
||||
@JsonIgnore
|
||||
public String getHashKey()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getWacn());
|
||||
sb.append(".").append(getSystem());
|
||||
sb.append(".").append(getValue());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||
@@ -48,6 +49,7 @@ import java.net.SocketAddress;
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
|
||||
@JsonSubTypes.Type(value = RdioScannerConfiguration.class, name="RdioScannerConfiguration"),
|
||||
@JsonSubTypes.Type(value = OpenMHzConfiguration.class, name="OpenMHzConfiguration"),
|
||||
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
|
||||
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
|
||||
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
|
||||
|
||||
@@ -25,6 +25,9 @@ import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfigura
|
||||
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerFeedConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzFeedConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
|
||||
@@ -67,6 +70,9 @@ public class BroadcastFactory
|
||||
case RDIOSCANNER_CALL:
|
||||
return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case OPENMHZ:
|
||||
return new OpenMHzBroadcaster((OpenMHzConfiguration)configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
case BROADCASTIFY:
|
||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
||||
inputAudioFormat, mp3Setting, aliasModel);
|
||||
@@ -109,6 +115,8 @@ public class BroadcastFactory
|
||||
return new RdioScannerConfiguration(format);
|
||||
case BROADCASTIFY:
|
||||
return new BroadcastifyFeedConfiguration(format);
|
||||
case OPENMHZ:
|
||||
return new OpenMHzConfiguration(format);
|
||||
case ICECAST_HTTP:
|
||||
return new IcecastHTTPConfiguration(format);
|
||||
case ICECAST_TCP:
|
||||
|
||||
@@ -32,6 +32,7 @@ public enum BroadcastServerType
|
||||
|
||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||
RDIOSCANNER_CALL("Rdio Scanner", "images/rdioscanner.png"),
|
||||
OPENMHZ("OpenMHz", "images/openmhz.png"),
|
||||
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||
|
||||
@@ -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
|
||||
@@ -38,9 +38,6 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@@ -48,12 +45,13 @@ import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
|
||||
@@ -475,7 +473,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||
|
||||
/**
|
||||
* HTTP headers used for posting to Rdio Scanner API
|
||||
*/
|
||||
public enum FormField
|
||||
{
|
||||
CALL("call"),
|
||||
FREQ("freq"),
|
||||
START_TIME("start_time"),
|
||||
STOP_TIME("stop_time"),
|
||||
CALL_LENGTH("call_length"),
|
||||
TALKGROUP_NUM("talkgroup_num"),
|
||||
EMERGENCY("emergency"),
|
||||
API_KEY("api_key"),
|
||||
SOURCE_LIST("source_list"),
|
||||
FREQ_LIST("freq_list"),
|
||||
PATCHES("patches");
|
||||
|
||||
private String mHeader;
|
||||
|
||||
FormField(String header)
|
||||
{
|
||||
mHeader = header;
|
||||
}
|
||||
|
||||
public String getHeader()
|
||||
{
|
||||
return mHeader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.google.common.base.Joiner;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.AudioRecording;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
* Audio broadcaster to push completed audio recordings to the OpenMHz call upload API.
|
||||
*
|
||||
*/
|
||||
public class OpenMHzBroadcaster extends AbstractAudioBroadcaster<OpenMHzConfiguration>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzBroadcaster.class);
|
||||
|
||||
private static final String ENCODING_TYPE_MP3 = "mp3";
|
||||
private static final String MULTIPART_TYPE = "multipart";
|
||||
private static final String DEFAULT_SUBTYPE = "form-data";
|
||||
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
|
||||
private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
|
||||
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
|
||||
private HttpClient mHttpClient = HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_2)
|
||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(20))
|
||||
.build();
|
||||
private long mLastConnectionAttempt;
|
||||
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
|
||||
private AliasModel mAliasModel;
|
||||
|
||||
/**
|
||||
* Constructs an instance of the broadcaster
|
||||
* @param config to use
|
||||
* @param aliasModel for access to aliases
|
||||
*/
|
||||
public OpenMHzBroadcaster(OpenMHzConfiguration config, InputAudioFormat inputAudioFormat,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(config);
|
||||
mAliasModel = aliasModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the audio recording processor thread
|
||||
*/
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTING);
|
||||
String response = testConnection(getBroadcastConfiguration());
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* OpenMHz API does not currently expose a test method.
|
||||
* TODO: FIX THIS
|
||||
*/
|
||||
if(response == "OK")// && response.toLowerCase().startsWith("<head><title>502 bad gateway</title></head>"))
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Error connecting to OpenMHz server on startup [" + response + "]");
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
|
||||
if(mAudioRecordingProcessorFuture == null)
|
||||
{
|
||||
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
|
||||
0, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the audio recording processor thread
|
||||
*/
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
if(mAudioRecordingProcessorFuture != null)
|
||||
{
|
||||
mAudioRecordingProcessorFuture.cancel(true);
|
||||
mAudioRecordingProcessorFuture = null;
|
||||
dispose();
|
||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares for disposal
|
||||
*/
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
AudioRecording audioRecording = mAudioRecordingQueue.poll();
|
||||
|
||||
while(audioRecording != null)
|
||||
{
|
||||
audioRecording.removePendingReplay();
|
||||
audioRecording = mAudioRecordingQueue.poll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this broadcaster continues to have successful connections to and transactions with the remote
|
||||
* server. If there is a connectivity or other issue, the broadcast state is set to temporary error and
|
||||
* the audio processor thread will persistently invoke this method to attempt a reconnect.
|
||||
*
|
||||
* OpenMHz does not have a test API endpoint, so we look for the incomplete call response.
|
||||
*/
|
||||
private boolean connected()
|
||||
{
|
||||
if(getBroadcastState() != BroadcastState.CONNECTED &&
|
||||
(System.currentTimeMillis() - mLastConnectionAttempt > mConnectionAttemptInterval))
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTING);
|
||||
|
||||
String response = testConnection(getBroadcastConfiguration());
|
||||
mLastConnectionAttempt = System.currentTimeMillis();
|
||||
|
||||
if(response != null && response == "200")
|
||||
{
|
||||
setBroadcastState(BroadcastState.CONNECTED);
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return getBroadcastState() == BroadcastState.CONNECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioQueueSize()
|
||||
{
|
||||
return mAudioRecordingQueue.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(AudioRecording audioRecording)
|
||||
{
|
||||
mAudioRecordingQueue.offer(audioRecording);
|
||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the audio recording is non-null and not too old, meaning that the age of the recording has not
|
||||
* exceeded the max age value indicated in the broadcast configuration. Audio recordings that are too old will be
|
||||
* deleted to ensure that the in-memory queue size doesn't blow up.
|
||||
* @param audioRecording to test
|
||||
* @return true if the recording is valid
|
||||
*/
|
||||
private boolean isValid(AudioRecording audioRecording)
|
||||
{
|
||||
return audioRecording != null && System.currentTimeMillis() - audioRecording.getStartTime() <=
|
||||
getBroadcastConfiguration().getMaximumRecordingAge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes any enqueued audio recordings. This method employs asynchronous
|
||||
* interaction with the server, so multiple audio recording uploads can occur simultaneously.
|
||||
*/
|
||||
private void processRecordingQueue()
|
||||
{
|
||||
|
||||
|
||||
while(connected() && !mAudioRecordingQueue.isEmpty())
|
||||
{
|
||||
final AudioRecording audioRecording = mAudioRecordingQueue.poll();
|
||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||
|
||||
if(isValid(audioRecording) && audioRecording.getRecordingLength() > 0)
|
||||
{
|
||||
int durationSeconds = (int)(audioRecording.getRecordingLength() / 1E3f);
|
||||
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
|
||||
String talkgroup = getTo(audioRecording);
|
||||
String radioId = getFrom(audioRecording);
|
||||
Long frequency = getFrequency(audioRecording);
|
||||
String patches = getPatches(audioRecording);
|
||||
String talkgroupLabel = getTalkgroupLabel(audioRecording);
|
||||
String talkgroupGroup = getTalkgroupGroup(audioRecording);
|
||||
String systemLabel = getSystemLabel(audioRecording);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] audioBytes = null;
|
||||
|
||||
try
|
||||
{
|
||||
audioBytes = Files.readAllBytes(audioRecording.getPath());
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("OpenMHz - audio recording file not found - ignoring upload");
|
||||
}
|
||||
|
||||
if(audioBytes != null)
|
||||
{
|
||||
String uri = getBroadcastConfiguration().getHost() + "/" + getBroadcastConfiguration().getSystemName() + "/upload";
|
||||
|
||||
OpenMHzBuilder bodyBuilder = new OpenMHzBuilder();
|
||||
bodyBuilder
|
||||
.addFile(audioBytes)
|
||||
.addPart(FormField.FREQ, frequency)
|
||||
.addPart(FormField.START_TIME, timestampSeconds)
|
||||
.addPart(FormField.STOP_TIME, timestampSeconds)
|
||||
.addPart(FormField.CALL_LENGTH, durationSeconds)
|
||||
.addPart(FormField.TALKGROUP_NUM, talkgroup)
|
||||
.addPart(FormField.EMERGENCY, 0)
|
||||
.addPart(FormField.API_KEY, getBroadcastConfiguration().getApiKey())
|
||||
// For future use if OpenMHz supports patch information
|
||||
//.addPart(FormField.PATCHES, patches)
|
||||
.addPart(FormField.SOURCE_LIST, "[{ \"pos\": 0.00, \"src\": " + radioId + "}]");
|
||||
|
||||
HttpRequest fileRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
|
||||
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||
.POST(bodyBuilder.build())
|
||||
.build();
|
||||
|
||||
mHttpClient.sendAsync(fileRequest, HttpResponse.BodyHandlers.ofString())
|
||||
.whenComplete((fileResponse, throwable1) -> {
|
||||
if(throwable1 != null || fileResponse.statusCode() != 200)
|
||||
{
|
||||
if(throwable1 instanceof IOException || throwable1 instanceof CompletionException)
|
||||
{
|
||||
//We get socket reset exceptions occasionally when the remote server doesn't
|
||||
//fully read our request and immediately responds.
|
||||
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||
mLog.error("OpenMHz API file upload fail [" +
|
||||
fileResponse.statusCode() + "] response [" +
|
||||
fileResponse.body() + "]");
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||
mLog.error("OpenMHz API file upload fail [" +
|
||||
fileResponse.statusCode() + "] response [" +
|
||||
fileResponse.body() + "]");
|
||||
}
|
||||
|
||||
incrementErrorAudioCount();
|
||||
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
|
||||
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if(fileResponse.statusCode() == 200)
|
||||
{
|
||||
incrementStreamedAudioCount();
|
||||
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
|
||||
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
|
||||
audioRecording.removePendingReplay();
|
||||
}
|
||||
else
|
||||
{
|
||||
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||
mLog.error("OpenMHz API file upload fail [" +
|
||||
fileResponse.statusCode() + "] response [" +
|
||||
fileResponse.body() + "]");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//Register an error for the file not found exception
|
||||
mLog.error("OpenMHz API - upload file not found [" +
|
||||
audioRecording.getPath().toString() + "]");
|
||||
incrementErrorAudioCount();
|
||||
broadcast(new BroadcastEvent(OpenMHzBroadcaster.this,
|
||||
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||
audioRecording.removePendingReplay();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Unknown Error", e);
|
||||
setBroadcastState(BroadcastState.ERROR);
|
||||
incrementErrorAudioCount();
|
||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||
audioRecording.removePendingReplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If we're not connected and there are recordings in the queue, check the recording at the head of the queue
|
||||
// and start age-off once the recordings become too old. The recordings should be time ordered in the queue.
|
||||
AudioRecording audioRecording = mAudioRecordingQueue.peek();
|
||||
|
||||
while(audioRecording != null)
|
||||
{
|
||||
if(isValid(audioRecording))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Remove the recording from the queue, remove a replay, and peek at the next recording in the queue
|
||||
mAudioRecordingQueue.poll();
|
||||
audioRecording.removePendingReplay();
|
||||
incrementAgedOffAudioCount();
|
||||
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
|
||||
audioRecording = mAudioRecordingQueue.peek();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a frequency value from the audio recording identifier collection.
|
||||
*/
|
||||
private static Long getFrequency(AudioRecording audioRecording)
|
||||
{
|
||||
Identifier identifier = audioRecording.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION,
|
||||
Form.CHANNEL_FREQUENCY, Role.ANY);
|
||||
|
||||
if(identifier instanceof ConfigurationLongIdentifier)
|
||||
{
|
||||
Long value = ((ConfigurationLongIdentifier)identifier).getValue();
|
||||
|
||||
if(value != null)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return Long.valueOf(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted string with the FROM identifier or uses a default of zero(0)
|
||||
*/
|
||||
private static String getFrom(AudioRecording audioRecording)
|
||||
{
|
||||
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
|
||||
{
|
||||
if(identifier instanceof RadioIdentifier)
|
||||
{
|
||||
return ((RadioIdentifier)identifier).getValue().toString();
|
||||
}
|
||||
}
|
||||
|
||||
return "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
|
||||
*
|
||||
*/
|
||||
private static String getTo(AudioRecording audioRecording)
|
||||
{
|
||||
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||
|
||||
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
|
||||
}
|
||||
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||
{
|
||||
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
|
||||
talkgroupIdentifier.getProtocol()));
|
||||
}
|
||||
else if(identifier instanceof RadioIdentifier radioIdentifier)
|
||||
{
|
||||
return radioIdentifier.getValue().toString();
|
||||
}
|
||||
|
||||
return "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted string with the Talkgroup Label from the Audio Recording Alias
|
||||
* If this is a PatchGroup we return only the first label as the primary talkgroup label.
|
||||
*
|
||||
*/
|
||||
private String getTalkgroupLabel(AudioRecording audioRecording)
|
||||
{
|
||||
|
||||
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if(identifier != null)
|
||||
{
|
||||
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||
if(!aliases.isEmpty())
|
||||
{
|
||||
sb.append(aliases.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted string with the Talkgroup Group from the Audio Recording Alias
|
||||
* If this is a PatchGroup we return only the first group as the primary talkgroup group.
|
||||
*
|
||||
*/
|
||||
private String getTalkgroupGroup(AudioRecording audioRecording)
|
||||
{
|
||||
|
||||
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if(identifier != null)
|
||||
{
|
||||
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||
if(!aliases.isEmpty())
|
||||
{
|
||||
sb.append(aliases.get(0).getGroup());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted string with the System Label from the Audio Recording Alias
|
||||
* If this is a PatchGroup we return only the first sytem as the primary talkgroup system.
|
||||
*
|
||||
*/
|
||||
private String getSystemLabel(AudioRecording audioRecording)
|
||||
{
|
||||
List<Identifier> systems = audioRecording.getIdentifierCollection().getIdentifiers(Form.SYSTEM);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if(!systems.isEmpty())
|
||||
{
|
||||
sb.append(systems.get(0));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted string with the patched talkgroups
|
||||
*/
|
||||
public static String getPatches(AudioRecording audioRecording)
|
||||
{
|
||||
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||
|
||||
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||
{
|
||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
|
||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
return "[]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests both the connection and configuration against the OpenMHz Call API service
|
||||
* @param configuration containing API key and system id
|
||||
* @return error string or null if test is successful
|
||||
*/
|
||||
public static String testConnection(OpenMHzConfiguration configuration)
|
||||
{
|
||||
String uri = configuration.getHost() + "/" + configuration.getSystemName() + "/authorize";
|
||||
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_1_1)
|
||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||
.connectTimeout(Duration.ofSeconds(20))
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(uri))
|
||||
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_FORM_URLENCODED)
|
||||
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||
.POST(HttpRequest.BodyPublishers.ofString("api_key="+configuration.getApiKey()))
|
||||
.build();
|
||||
|
||||
HttpResponse.BodyHandler<String> responseHandler = HttpResponse.BodyHandlers.ofString();
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponse<String> response = httpClient.send(request, responseHandler);
|
||||
String responseBody = response.body();
|
||||
if (response.statusCode() == 200)
|
||||
{
|
||||
return "OK";
|
||||
}
|
||||
else if(response.statusCode() == 403)
|
||||
{
|
||||
return "Invalid API Key";
|
||||
}
|
||||
else if(response.statusCode() == 500)
|
||||
{
|
||||
return "Invalid System Name";
|
||||
}
|
||||
|
||||
return "No Response";
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return e.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioRecordingProcessor implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
processRecordingQueue();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.debug("Starting ...");
|
||||
|
||||
OpenMHzConfiguration config = new OpenMHzConfiguration();
|
||||
config.setHost("https://api.OpenMHz.com/call-upload-dev");
|
||||
config.setApiKey("c33aae37-8572-11ea-bd8b-0ecc8ab9ccec");
|
||||
config.setSystemName("systemx");
|
||||
|
||||
String response = testConnection(config);
|
||||
mLog.error("Response: " + response);
|
||||
|
||||
if(response == "OK")
|
||||
{
|
||||
mLog.debug("Test Successful!");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(response.contains("Invalid API Key"))
|
||||
{
|
||||
mLog.error("Invalid API Key");
|
||||
}
|
||||
else if(response.contains("Invalid System Name"))
|
||||
{
|
||||
mLog.error("Invalid System Name");
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.debug("Response: " + response);
|
||||
}
|
||||
}
|
||||
|
||||
mLog.debug("Finished!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||
|
||||
import java.net.http.HttpRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.lang.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Builder for an HTTP body publisher to produce a OpenMHz call event
|
||||
*/
|
||||
public class OpenMHzBuilder
|
||||
{
|
||||
private static final String DASH_DASH = "--";
|
||||
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
|
||||
private List<Part> mParts = new ArrayList<>();
|
||||
private byte[] audioBytes = null;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public OpenMHzBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the static multi-part boundary string
|
||||
*/
|
||||
public String getBoundary()
|
||||
{
|
||||
return BOUNDARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Audio bytes part to the call
|
||||
*/
|
||||
public OpenMHzBuilder addFile(byte[] value)
|
||||
{
|
||||
audioBytes = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a string part to the call
|
||||
*/
|
||||
public OpenMHzBuilder addPart(FormField key, String value)
|
||||
{
|
||||
if(key != null && value != null)
|
||||
{
|
||||
mParts.add(new Part(key.getHeader(), value));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a number part to the call
|
||||
*/
|
||||
public OpenMHzBuilder addPart(FormField key, Number value)
|
||||
{
|
||||
if(key != null && value != null)
|
||||
{
|
||||
mParts.add(new Part(key.getHeader(), value.toString()));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form-data item
|
||||
*/
|
||||
private static String formatPart(Part part, String boundary)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||
sb.append("Content-Disposition: form-data; name=\"").append(part.mKey).append("\"\r\n\r\n");
|
||||
sb.append(part.getValue()).append("\r\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the audio file item
|
||||
*/
|
||||
private String formatFilePart(String boundary)
|
||||
{
|
||||
|
||||
if(audioBytes == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb= new StringBuilder();
|
||||
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||
sb.append("Content-Disposition: form-data; name=\"").append("call").append("\"; filename=\"audio.mp3\"\r\n\r\n");
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the boundary closing item
|
||||
*/
|
||||
private static String getClosingBoundary(String boundary)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(DASH_DASH).append(boundary).append(DASH_DASH).append("\r\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BodyPublisher for accessing the call form data
|
||||
*/
|
||||
public HttpRequest.BodyPublisher build()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(Part part: mParts)
|
||||
{
|
||||
sb.append(formatPart(part, BOUNDARY));
|
||||
}
|
||||
|
||||
sb.append(formatFilePart(BOUNDARY));
|
||||
|
||||
|
||||
/**
|
||||
* We need to create a ByteArray consisting of the Sting "parts" and the audio file bytes
|
||||
*/
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
outputStream.write(sb.toString().getBytes());
|
||||
|
||||
if(audioBytes != null)
|
||||
{
|
||||
outputStream.write(audioBytes);
|
||||
}
|
||||
|
||||
sb = new StringBuilder();
|
||||
sb.append("\r\n");
|
||||
sb.append(getClosingBoundary(BOUNDARY));
|
||||
|
||||
outputStream.write(sb.toString().getBytes());
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
//mLog.error("Rdio Scanner API - unable to create POST reqeust.");
|
||||
}
|
||||
|
||||
return HttpRequest.BodyPublishers.ofByteArray(outputStream.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(Part part: mParts)
|
||||
{
|
||||
sb.append(formatPart(part, BOUNDARY));
|
||||
}
|
||||
|
||||
sb.append(getClosingBoundary(BOUNDARY));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Key:Value pair holder
|
||||
*/
|
||||
public class Part
|
||||
{
|
||||
private String mKey;
|
||||
private String mValue;
|
||||
|
||||
/**
|
||||
* Constructs a new part
|
||||
* @param key value
|
||||
* @param value item
|
||||
*/
|
||||
public Part(String key, String value)
|
||||
{
|
||||
mKey = key;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public String getKey()
|
||||
{
|
||||
return mKey;
|
||||
}
|
||||
|
||||
public String getValue()
|
||||
{
|
||||
return mValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Streaming configuration for Rdio Scanner API.
|
||||
*
|
||||
* Note: this API is not a streaming audio service, rather a completed call push service. However, it fits nicely
|
||||
* with the structure of the audio streaming subsystem in sdrtrunk
|
||||
*/
|
||||
public class OpenMHzConfiguration extends BroadcastConfiguration
|
||||
{
|
||||
public static final String PRODUCTION_ENDPOINT = "https://api.openmhz.com";
|
||||
|
||||
private StringProperty mSystemName = new SimpleStringProperty();
|
||||
private StringProperty mApiKey = new SimpleStringProperty();
|
||||
|
||||
/**
|
||||
* Constructor for faster jackson
|
||||
*/
|
||||
public OpenMHzConfiguration()
|
||||
{
|
||||
this(BroadcastFormat.MP3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public constructor.
|
||||
* @param format to use for audio recording (MP3)
|
||||
*/
|
||||
public OpenMHzConfiguration(BroadcastFormat format)
|
||||
{
|
||||
super(format);
|
||||
|
||||
if(getHost() == null)
|
||||
{
|
||||
setHost(PRODUCTION_ENDPOINT);
|
||||
}
|
||||
|
||||
//The parent class binds this property, so we unbind it and rebind it here
|
||||
mValid.unbind();
|
||||
mValid.bind(Bindings.and(Bindings.and(Bindings.isNotNull(mSystemName), Bindings.isNotNull(mApiKey)),
|
||||
Bindings.isNotNull(mHost)));
|
||||
}
|
||||
|
||||
/**
|
||||
* System ID as a property
|
||||
*/
|
||||
public StringProperty systemIDProperty()
|
||||
{
|
||||
return mSystemName;
|
||||
}
|
||||
|
||||
/**
|
||||
* API key as a property
|
||||
*/
|
||||
public StringProperty apiKeyProperty()
|
||||
{
|
||||
return mApiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Key
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "api_key")
|
||||
public String getApiKey()
|
||||
{
|
||||
return mApiKey.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the api key
|
||||
* @param apiKey
|
||||
*/
|
||||
public void setApiKey(String apiKey)
|
||||
{
|
||||
mApiKey.setValue(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* System name as provided by OpenMHz.com
|
||||
*/
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "system_name")
|
||||
public String getSystemName()
|
||||
{
|
||||
return mSystemName.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the system name provided by OpenMHz.com
|
||||
*/
|
||||
public void setSystemName(String systemName)
|
||||
{
|
||||
mSystemName.set(systemName);
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
public BroadcastServerType getBroadcastServerType()
|
||||
{
|
||||
return BroadcastServerType.OPENMHZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastConfiguration copyOf()
|
||||
{
|
||||
OpenMHzConfiguration copy = new OpenMHzConfiguration();
|
||||
copy.setSystemName(getSystemName());
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.playlist.streaming;
|
||||
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzBroadcaster;
|
||||
import io.github.dsheirer.audio.broadcast.openmhz.OpenMHzConfiguration;
|
||||
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;
|
||||
|
||||
/**
|
||||
* OpenMHz calls API configuration editor
|
||||
*/
|
||||
public class OpenMHzEditor extends AbstractBroadcastEditor<OpenMHzConfiguration>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzEditor.class);
|
||||
private TextField mSystemNameTextField;
|
||||
private IntegerTextField mMaxAgeTextField;
|
||||
private TextField mApiKeyTextField;
|
||||
private TextField mHostTextField;
|
||||
private GridPane mEditorPane;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param playlistManager for accessing the broadcast model
|
||||
*/
|
||||
public OpenMHzEditor(PlaylistManager playlistManager)
|
||||
{
|
||||
super(playlistManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(OpenMHzConfiguration item)
|
||||
{
|
||||
super.setItem(item);
|
||||
|
||||
getSystemNameTextField().setDisable(item == null);
|
||||
getApiKeyTextField().setDisable(item == null);
|
||||
getHostTextField().setDisable(item == null);
|
||||
getMaxAgeTextField().setDisable(item == null);
|
||||
|
||||
if(item != null)
|
||||
{
|
||||
getSystemNameTextField().setText(item.getSystemName());
|
||||
getApiKeyTextField().setText(item.getApiKey());
|
||||
getHostTextField().setText(item.getHost());
|
||||
getMaxAgeTextField().set((int)(item.getMaximumRecordingAge() / 1000));
|
||||
}
|
||||
else
|
||||
{
|
||||
getSystemNameTextField().setText(null);
|
||||
getApiKeyTextField().setText(null);
|
||||
getHostTextField().setText(null);
|
||||
getMaxAgeTextField().set(0);
|
||||
}
|
||||
|
||||
modifiedProperty().set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
if(getItem() != null)
|
||||
{
|
||||
getItem().setSystemName(getSystemNameTextField().getText());
|
||||
getItem().setHost(getHostTextField().getText());
|
||||
getItem().setApiKey(getApiKeyTextField().getText());
|
||||
getItem().setMaximumRecordingAge(getMaxAgeTextField().get() * 1000);
|
||||
}
|
||||
|
||||
super.save();
|
||||
}
|
||||
|
||||
public BroadcastServerType getBroadcastServerType()
|
||||
{
|
||||
return BroadcastServerType.OPENMHZ;
|
||||
}
|
||||
|
||||
protected GridPane getEditorPane()
|
||||
{
|
||||
if(mEditorPane == null)
|
||||
{
|
||||
mEditorPane = new GridPane();
|
||||
mEditorPane.setPadding(new Insets(10, 5, 10,10));
|
||||
mEditorPane.setVgap(10);
|
||||
mEditorPane.setHgap(5);
|
||||
|
||||
int row = 0;
|
||||
|
||||
Label formatLabel = new Label("Format");
|
||||
GridPane.setHalignment(formatLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(formatLabel, 0, row);
|
||||
mEditorPane.getChildren().add(formatLabel);
|
||||
|
||||
GridPane.setConstraints(getFormatField(), 1, row);
|
||||
mEditorPane.getChildren().add(getFormatField());
|
||||
|
||||
Label enabledLabel = new Label("Enabled");
|
||||
GridPane.setHalignment(enabledLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(enabledLabel, 2, row);
|
||||
mEditorPane.getChildren().add(enabledLabel);
|
||||
|
||||
GridPane.setConstraints(getEnabledSwitch(), 3, row);
|
||||
mEditorPane.getChildren().add(getEnabledSwitch());
|
||||
|
||||
Label systemLabel = new Label("Name");
|
||||
GridPane.setHalignment(systemLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(systemLabel, 0, ++row);
|
||||
mEditorPane.getChildren().add(systemLabel);
|
||||
|
||||
GridPane.setConstraints(getNameTextField(), 1, row);
|
||||
mEditorPane.getChildren().add(getNameTextField());
|
||||
|
||||
Label apiKeyLabel = new Label("API Key");
|
||||
GridPane.setHalignment(apiKeyLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(apiKeyLabel, 0, ++row);
|
||||
mEditorPane.getChildren().add(apiKeyLabel);
|
||||
|
||||
GridPane.setConstraints(getApiKeyTextField(), 1, row);
|
||||
mEditorPane.getChildren().add(getApiKeyTextField());
|
||||
|
||||
Label systemNameLabel = new Label("System Short Name");
|
||||
GridPane.setHalignment(systemNameLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(systemNameLabel, 0, ++row);
|
||||
mEditorPane.getChildren().add(systemNameLabel);
|
||||
|
||||
GridPane.setConstraints(getSystemNameTextField(), 1, row);
|
||||
mEditorPane.getChildren().add(getSystemNameTextField());
|
||||
|
||||
Label hostLabel = new Label("OpenMHz Upload Server (API)");
|
||||
GridPane.setHalignment(hostLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(hostLabel, 0, ++row);
|
||||
mEditorPane.getChildren().add(hostLabel);
|
||||
|
||||
GridPane.setConstraints(getHostTextField(), 1, row);
|
||||
mEditorPane.getChildren().add(getHostTextField());
|
||||
|
||||
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
|
||||
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(maxAgeLabel, 0, ++row);
|
||||
mEditorPane.getChildren().add(maxAgeLabel);
|
||||
|
||||
GridPane.setConstraints(getMaxAgeTextField(), 1, row);
|
||||
mEditorPane.getChildren().add(getMaxAgeTextField());
|
||||
|
||||
}
|
||||
|
||||
return mEditorPane;
|
||||
}
|
||||
|
||||
private IntegerTextField getMaxAgeTextField()
|
||||
{
|
||||
if(mMaxAgeTextField == null)
|
||||
{
|
||||
mMaxAgeTextField = new IntegerTextField();
|
||||
mMaxAgeTextField.setDisable(true);
|
||||
mMaxAgeTextField.textProperty().addListener(mEditorModificationListener);
|
||||
}
|
||||
|
||||
return mMaxAgeTextField;
|
||||
}
|
||||
|
||||
private TextField getHostTextField()
|
||||
{
|
||||
if(mHostTextField == null)
|
||||
{
|
||||
mHostTextField = new TextField();
|
||||
mHostTextField.setDisable(true);
|
||||
mHostTextField.textProperty().addListener(mEditorModificationListener);
|
||||
}
|
||||
|
||||
return mHostTextField;
|
||||
}
|
||||
|
||||
private TextField getApiKeyTextField()
|
||||
{
|
||||
if(mApiKeyTextField == null)
|
||||
{
|
||||
mApiKeyTextField = new TextField();
|
||||
mApiKeyTextField.setDisable(true);
|
||||
mApiKeyTextField.textProperty().addListener(mEditorModificationListener);
|
||||
}
|
||||
|
||||
return mApiKeyTextField;
|
||||
}
|
||||
|
||||
private TextField getSystemNameTextField()
|
||||
{
|
||||
if(mSystemNameTextField == null)
|
||||
{
|
||||
mSystemNameTextField = new TextField();
|
||||
mSystemNameTextField.setDisable(true);
|
||||
mSystemNameTextField.textProperty().addListener(mEditorModificationListener);
|
||||
}
|
||||
|
||||
return mSystemNameTextField;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast.openmhz;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
|
||||
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class OpenMHzFeedConfiguration extends IcecastTCPConfiguration
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(OpenMHzFeedConfiguration.class);
|
||||
|
||||
private int mFeedID;
|
||||
|
||||
public OpenMHzFeedConfiguration()
|
||||
{
|
||||
//No-arg constructor for JAXB
|
||||
this(BroadcastFormat.MP3);
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenMHz configuration for Icecast 2.3.2 compatible servers
|
||||
*
|
||||
* @param format of output audio (MP3)
|
||||
*/
|
||||
public OpenMHzFeedConfiguration(BroadcastFormat format)
|
||||
{
|
||||
super(format);
|
||||
|
||||
setBitRate(16);
|
||||
setChannels(1);
|
||||
setSampleRate(8000);
|
||||
setInline(true);
|
||||
}
|
||||
|
||||
public static OpenMHzFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
|
||||
{
|
||||
OpenMHzFeedConfiguration config = new OpenMHzFeedConfiguration(BroadcastFormat.MP3);
|
||||
config.setName(userFeedBroadcast.getDescription());
|
||||
config.setHost(userFeedBroadcast.getHostname());
|
||||
config.setMountPoint(userFeedBroadcast.getMount());
|
||||
config.setFeedID(userFeedBroadcast.getFeedId());
|
||||
config.setPassword(userFeedBroadcast.getPassword());
|
||||
|
||||
try
|
||||
{
|
||||
config.setPort(Integer.parseInt(userFeedBroadcast.getPort()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error creating Rdio Scanner configuration from radio reference user feed instance");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BroadcastConfiguration copyOf()
|
||||
{
|
||||
OpenMHzFeedConfiguration copy = new OpenMHzFeedConfiguration(getBroadcastFormat());
|
||||
|
||||
//Broadcast Configuration Parameters
|
||||
copy.setName(getName());
|
||||
copy.setHost(getHost());
|
||||
copy.setPort(getPort());
|
||||
copy.setInline(getInline());
|
||||
copy.setPassword(getPassword());
|
||||
copy.setDelay(getDelay());
|
||||
copy.setEnabled(false);
|
||||
|
||||
//Icecast Configuration Parameters
|
||||
copy.setUserName(getUserName());
|
||||
copy.setMountPoint(getMountPoint());
|
||||
copy.setDescription(getDescription());
|
||||
copy.setGenre(getGenre());
|
||||
copy.setPublic(isPublic());
|
||||
copy.setURL(getURL());
|
||||
|
||||
//OpenMHz Configuration Parameters
|
||||
copy.setFeedID(getFeedID());
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||
@Override
|
||||
public BroadcastServerType getBroadcastServerType()
|
||||
{
|
||||
return BroadcastServerType.OPENMHZ;
|
||||
}
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "feed_id")
|
||||
public int getFeedID()
|
||||
{
|
||||
return mFeedID;
|
||||
}
|
||||
|
||||
public void setFeedID(int feedID)
|
||||
{
|
||||
mFeedID = feedID;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -20,7 +20,6 @@
|
||||
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import com.google.common.base.Joiner;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
@@ -34,7 +33,6 @@ import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
|
||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||
@@ -42,24 +40,12 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
@@ -67,6 +53,8 @@ import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
@@ -244,6 +232,9 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
|
||||
String talkgroupLabel = getTalkgroupLabel(audioRecording);
|
||||
String talkgroupGroup = getTalkgroupGroup(audioRecording);
|
||||
String systemLabel = getSystemLabel(audioRecording);
|
||||
String path = audioRecording.getPath().toString();
|
||||
// Remove TEMPORARY_STREAM_FILE_SUFFIX
|
||||
String audioName = path.substring(path.substring(0, path.lastIndexOf("_")).lastIndexOf("_") + 1);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -264,6 +255,7 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
|
||||
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
|
||||
bodyBuilder.addPart(FormField.KEY, getBroadcastConfiguration().getApiKey())
|
||||
.addPart(FormField.SYSTEM, getBroadcastConfiguration().getSystemID())
|
||||
.addAudioName(audioName)
|
||||
.addFile(audioBytes)
|
||||
.addPart(FormField.DATE_TIME, timestampSeconds)
|
||||
.addPart(FormField.TALKGROUP_ID, talkgroup)
|
||||
@@ -278,7 +270,6 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
|
||||
.uri(URI.create(getBroadcastConfiguration().getHost()))
|
||||
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
|
||||
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||
.header(HttpHeaders.CONTENT_TYPE, "audio/mpeg")
|
||||
.POST(bodyBuilder.build())
|
||||
.build();
|
||||
|
||||
@@ -525,7 +516,12 @@ public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScanner
|
||||
|
||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||
{
|
||||
sb.append(",").append(patched.getValue());
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ public class RdioScannerBuilder
|
||||
private static final String DASH_DASH = "--";
|
||||
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
|
||||
private List<Part> mParts = new ArrayList<>();
|
||||
private byte[] audioBytes = null;
|
||||
private byte[] audioBytes = null;
|
||||
private String audioName = null;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -51,6 +52,15 @@ public class RdioScannerBuilder
|
||||
return BOUNDARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Audio file name part to the call
|
||||
*/
|
||||
public RdioScannerBuilder addAudioName(String value)
|
||||
{
|
||||
audioName = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Audio bytes part to the call
|
||||
*/
|
||||
@@ -110,7 +120,8 @@ public class RdioScannerBuilder
|
||||
}
|
||||
StringBuilder sb= new StringBuilder();
|
||||
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||
sb.append("Content-Disposition: form-data; name=\"").append("audio").append("\"\r\n\r\n");
|
||||
sb.append("Content-Disposition: form-data; filename=\"").append(audioName);
|
||||
sb.append("\"; name=\"").append("audio").append("\"\r\n\r\n");
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ package io.github.dsheirer.bits;
|
||||
|
||||
import io.github.dsheirer.edac.CRC;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.BitSet;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
@@ -276,8 +277,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,13 +845,18 @@ public class BinaryMessage extends BitSet
|
||||
*/
|
||||
public int getTwosComplement(int start, int end)
|
||||
{
|
||||
BinaryMessage sub = getSubMessage(start, end);
|
||||
boolean negative = sub.get(start);
|
||||
sub.flip(0, sub.size());
|
||||
|
||||
int value = sub.getInt(0, sub.size()) + 1;
|
||||
value *= (negative ? -1 : 1);
|
||||
return value;
|
||||
if(get(start))
|
||||
{
|
||||
//Negative value - flip and add one
|
||||
BinaryMessage fragment = getSubMessage(start, end);
|
||||
fragment.flip(0, fragment.size());
|
||||
return fragment.getInt(1, fragment.size()) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Positive value - return the contents.
|
||||
return getInt(start + 1, end);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1274,7 +1279,7 @@ public class BinaryMessage extends BitSet
|
||||
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||
}
|
||||
|
||||
return new String(bytes);
|
||||
return new String(bytes, Charset.forName("UTF-16"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -132,12 +132,10 @@ public enum SyncPattern
|
||||
true, true, false, false //1100 0xC
|
||||
}),
|
||||
|
||||
/* Sync (0x158) = 1101011000 */
|
||||
/* Sync (0x158) = 101011000 */
|
||||
PASSPORT( new boolean[]
|
||||
{
|
||||
true, //0001 0x1
|
||||
false, true, false, true, //0101 0x5
|
||||
true, false, false, false //1000 0x8
|
||||
true,false,true,false,true,true,false,false,false
|
||||
} ),
|
||||
|
||||
LTR_STANDARD_OSW( new boolean[]
|
||||
|
||||
@@ -22,6 +22,8 @@ package io.github.dsheirer.buffer;
|
||||
import io.github.dsheirer.sample.SampleUtils;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
@@ -30,6 +32,7 @@ import java.util.Iterator;
|
||||
public class FloatNativeBuffer extends AbstractNativeBuffer
|
||||
{
|
||||
private float[] mInterleavedComplexSamples;
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -81,48 +84,51 @@ public class FloatNativeBuffer extends AbstractNativeBuffer
|
||||
|
||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||
{
|
||||
private boolean mEmpty;
|
||||
private int mBufferPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return !mEmpty;
|
||||
return mBufferPointer < mInterleavedComplexSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mEmpty)
|
||||
if(!hasNext())
|
||||
{
|
||||
throw new IllegalStateException("No more samples");
|
||||
}
|
||||
|
||||
mEmpty = true;
|
||||
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
|
||||
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
||||
mBufferPointer += BUFFER_SIZE * 2;
|
||||
|
||||
return SampleUtils.deinterleave(chunk, getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||
{
|
||||
private boolean mEmpty;
|
||||
|
||||
private int mBufferPointer = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return !mEmpty;
|
||||
return mBufferPointer < mInterleavedComplexSamples.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mEmpty)
|
||||
if(!hasNext())
|
||||
{
|
||||
throw new IllegalStateException("No more samples");
|
||||
}
|
||||
|
||||
mEmpty = true;
|
||||
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
|
||||
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
||||
mBufferPointer += BUFFER_SIZE * 2;
|
||||
|
||||
return new InterleavedComplexSamples(chunk, getTimestamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,52 +33,105 @@ import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
/**
|
||||
* Swing panel for Now Playing channels table and channel details tab set.
|
||||
*/
|
||||
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;
|
||||
private JideTabbedPane mTabbedPane;
|
||||
private JideSplitPane mSplitPane;
|
||||
private boolean mDetailTabsVisible;
|
||||
|
||||
/**
|
||||
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
|
||||
* messages, events, and spectral view.
|
||||
*/
|
||||
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences,
|
||||
SettingsManager settingsManager)
|
||||
SettingsManager settingsManager, boolean detailTabsVisible)
|
||||
{
|
||||
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
|
||||
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
|
||||
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
|
||||
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
|
||||
mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager);
|
||||
mDetailTabsVisible = detailTabsVisible;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the visibility of the channel details tabs panel.
|
||||
* @param visible true to show or false to hide.
|
||||
*/
|
||||
public void setDetailTabsVisible(boolean visible)
|
||||
{
|
||||
//Only adjust if there is a change in state
|
||||
if(visible ^ mDetailTabsVisible)
|
||||
{
|
||||
mDetailTabsVisible = visible;
|
||||
|
||||
if(mDetailTabsVisible)
|
||||
{
|
||||
getSplitPane().add(getTabbedPane());
|
||||
}
|
||||
else
|
||||
{
|
||||
getSplitPane().remove(getTabbedPane());
|
||||
}
|
||||
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private JideTabbedPane getTabbedPane()
|
||||
{
|
||||
if(mTabbedPane == null)
|
||||
{
|
||||
mTabbedPane = new JideTabbedPane();
|
||||
mTabbedPane.addTab("Details", mChannelDetailPanel);
|
||||
mTabbedPane.addTab("Events", mDecodeEventPanel);
|
||||
mTabbedPane.addTab("Messages", mMessageActivityPanel);
|
||||
mTabbedPane.addTab("Channel", mChannelPowerPanel);
|
||||
mTabbedPane.setFont(this.getFont());
|
||||
mTabbedPane.setForeground(Color.BLACK);
|
||||
//Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing
|
||||
mTabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(getTabbedPane().getSelectedIndex() == getTabbedPane()
|
||||
.indexOfComponent(mChannelPowerPanel)));
|
||||
}
|
||||
|
||||
return mTabbedPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split pane for channels table and channel details tabs.
|
||||
*/
|
||||
private JideSplitPane getSplitPane()
|
||||
{
|
||||
if(mSplitPane == null)
|
||||
{
|
||||
mSplitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
|
||||
mSplitPane.setShowGripper(true);
|
||||
}
|
||||
|
||||
return mSplitPane;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[grow,fill]") );
|
||||
getSplitPane().add(mChannelMetadataPanel);
|
||||
|
||||
JideTabbedPane tabbedPane = new JideTabbedPane();
|
||||
tabbedPane.addTab("Details", mChannelDetailPanel);
|
||||
tabbedPane.addTab("Events", mDecodeEventPanel);
|
||||
tabbedPane.addTab("Messages", mMessageActivityPanel);
|
||||
tabbedPane.addTab("Channel", mChannelPowerPanel);
|
||||
tabbedPane.setFont(this.getFont());
|
||||
tabbedPane.setForeground(Color.BLACK);
|
||||
|
||||
//Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing
|
||||
tabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(tabbedPane.getSelectedIndex() == tabbedPane
|
||||
.indexOfComponent(mChannelPowerPanel)));
|
||||
|
||||
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
|
||||
splitPane.setShowGripper(true);
|
||||
splitPane.add(mChannelMetadataPanel);
|
||||
splitPane.add(tabbedPane);
|
||||
add(splitPane);
|
||||
if(mDetailTabsVisible)
|
||||
{
|
||||
getSplitPane().add(getTabbedPane());
|
||||
}
|
||||
|
||||
add(getSplitPane());
|
||||
mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelDetailPanel);
|
||||
mChannelMetadataPanel.addProcessingChainSelectionListener(mDecodeEventPanel);
|
||||
mChannelMetadataPanel.addProcessingChainSelectionListener(mMessageActivityPanel);
|
||||
|
||||
@@ -59,17 +59,25 @@ public class ControllerPanel extends JPanel
|
||||
|
||||
public ControllerPanel(PlaylistManager playlistManager, AudioPlaybackManager audioPlaybackManager,
|
||||
IconModel iconModel, MapService mapService, SettingsManager settingsManager,
|
||||
TunerManager tunerManager, UserPreferences userPreferences)
|
||||
TunerManager tunerManager, UserPreferences userPreferences, boolean detailTabsVisible)
|
||||
{
|
||||
mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager,
|
||||
playlistManager.getAliasModel());
|
||||
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager);
|
||||
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager, detailTabsVisible);
|
||||
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager);
|
||||
mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Now playing panel.
|
||||
*/
|
||||
public NowPlayingPanel getNowPlayingPanel()
|
||||
{
|
||||
return mNowPlayingPanel;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[]0[grow,fill]0[]"));
|
||||
@@ -97,8 +105,8 @@ public class ControllerPanel extends JPanel
|
||||
mTabbedPane.addTab("Map", mMapPanel);
|
||||
mTabbedPane.addTab("Tuners", mTunerManagerPanel);
|
||||
|
||||
Icon gearIcon = IconFontSwing.buildIcon(FontAwesome.COG, 20, Color.DARK_GRAY);
|
||||
mTabbedPane.addTab("Playlist Editor", gearIcon, new JLabel("Show Playlist Manager"));
|
||||
Icon playIcon = IconFontSwing.buildIcon(FontAwesome.PLAY_CIRCLE_O, 20, Color.DARK_GRAY);
|
||||
mTabbedPane.addTab("Playlist Editor", playIcon, new JLabel("Show Playlist Manager"));
|
||||
mSettingsTabIndex = mTabbedPane.getTabCount() - 1;
|
||||
|
||||
//Set preferred size to influence the split between these panels
|
||||
|
||||
@@ -365,7 +365,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
||||
* @param request containing channel and other details
|
||||
* @throws ChannelException if a source is not available for the channel
|
||||
*/
|
||||
private void startProcessing(ChannelStartProcessingRequest request) throws ChannelException
|
||||
private synchronized void startProcessing(ChannelStartProcessingRequest request) throws ChannelException
|
||||
{
|
||||
Channel channel = request.getChannel();
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
|
||||
|
||||
//Sized to process 1/20th batch of 152 buffers a second, at 40 times per second
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(152 / 20, 25);
|
||||
//Sized to process 40 times per second
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(25);
|
||||
private FloatFFT_1D mFFT;
|
||||
private float[] mInlineSamples;
|
||||
private float[] mInlineFilter;
|
||||
@@ -406,9 +406,9 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
*/
|
||||
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
||||
{
|
||||
public IFFTProcessorDispatcher(int batchSize, long interval)
|
||||
public IFFTProcessorDispatcher(long interval)
|
||||
{
|
||||
super("sdrtrunk polyphase ifft processor", batchSize, interval);
|
||||
super("sdrtrunk polyphase ifft processor", interval);
|
||||
|
||||
//We create a listener interface to receive the batched channel results arrays from the scheduled thread pool
|
||||
//dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each
|
||||
|
||||
@@ -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;
|
||||
@@ -108,7 +110,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||
}
|
||||
|
||||
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 50, 10);
|
||||
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 10);
|
||||
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
||||
mInputChannelCount = inputChannelCount;
|
||||
//Process 1/10th of the sample rate per second at a rate of 20 times a second (200% of anticipated rate)
|
||||
mHeartbeatManager = heartbeatManager;
|
||||
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel", (int)(sampleRate / 10),
|
||||
50, mHeartbeatManager);
|
||||
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel",50, mHeartbeatManager);
|
||||
mChannelResultsDispatcher.setListener(floats -> {
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -16,61 +16,64 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.fsk;
|
||||
|
||||
import io.github.dsheirer.bits.MessageFramer;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.dc.IIRSinglePoleDCRemovalFilter;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.Arrays;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Logic Trunked Radio (LTR) 300-baud FSK decoder is designed to operate against an 8 kHz FM demodulated audio stream.
|
||||
* LTR decoder designed to work with 8 kHz input audio samples and decode 300-baud signalling.
|
||||
*
|
||||
* This decoder employs a zero-crossing timing error detector with adjustable symbol timing error gain. This decoder
|
||||
* also employs a synchronization monitor that automatically adjusts symbol timing error gain according to the quantity
|
||||
* of successful message sync pattern detections. This is optimized for LTR signals that periodically transmit idle
|
||||
* bursts and transmit continuous message streams during voice transmissions. The synchronization monitor adjusts to an
|
||||
* aggressive gain value when sync is lost and automatically reduces timing error gain as successive sync pattern
|
||||
* detections occur.
|
||||
* This decoder monitors the incoming sample stream looking for either a positive or negative sequence of samples where
|
||||
* the energy delta across the baud period exceeds a threshold value and causes the symbol state to flip. The symbol
|
||||
* state remains across successive baud periods until the next state change sequence is detected.
|
||||
*
|
||||
* FM demodulated sample buffers should not be filtered prior to this decoder. An internal DC-removal filter coupled
|
||||
* with a low-pass filter removes any tuning offset and/or audio components above 300 Hertz. The DC removal filter is
|
||||
* adjusted for a slower offset removal in order to prevent overly distorting the FSK signalling component. This mild
|
||||
* adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However,
|
||||
* the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission.
|
||||
* Valid LTR messages start with a zero symbol and transition to a positive symbol for the sync bit, this decoder
|
||||
* monitors for excessively long sequences of a one symbol and forces the symbol state back to a zero.
|
||||
*/
|
||||
public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISyncStateListener
|
||||
public class LTRDecoder implements Listener<float[]>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
||||
|
||||
public static final float SAMPLES_PER_SYMBOL = 8000.0f / 300.0f;
|
||||
|
||||
protected static final float COARSE_TIMING_GAIN = 1.0f / 3.0f;
|
||||
protected static final float MEDIUM_TIMING_GAIN = 1.0f / 4.0f;
|
||||
protected static final float FINE_TIMING_GAIN = 1.0f / 5.0f;
|
||||
private static final Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
||||
//Slope calculation sumXX value is always the same for 27 evenly spaced samples
|
||||
private static final double SLOPE_CALCULATION_SUM_XX = 1637.999992057681;
|
||||
private static final float BAUD_LENGTH = 8000.0f / 300.0f;
|
||||
private static final float SLOPE_THRESHOLD = 0.0048f;
|
||||
private static final int MAX_ONES_SEQUENCE = 18;
|
||||
private static final int OVERLAP = 26;
|
||||
private static final int POST_TRANSITION_SAMPLES_TO_SKIP = 26;
|
||||
private static final int SLOPE_CALCULATION_LENGTH = 27;
|
||||
private static final int SYMBOL_TRANSITION_IDEAL_MIN = 11;
|
||||
private static final int SYMBOL_TRANSITION_IDEAL_MAX = 16;
|
||||
|
||||
private static float[] sLowPassFilterCoefficients;
|
||||
private boolean mSymbol = false;
|
||||
private float[] mResidual = new float[OVERLAP];
|
||||
private float mBaudCounter = 0f;
|
||||
private float mMaxSlope = 0;
|
||||
private int mExcessiveOneSequenceCounter = 0;
|
||||
private Listener<boolean[]> mSymbolListener;
|
||||
|
||||
static
|
||||
{
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(8000)
|
||||
.gridDensity(16)
|
||||
.oddLength(true)
|
||||
.passBandCutoff(300)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(500)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
||||
.build();
|
||||
.sampleRate(8000)
|
||||
.gridDensity(16)
|
||||
.oddLength(true)
|
||||
.passBandCutoff(300)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(500)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
||||
.build();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -87,143 +90,193 @@ public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISync
|
||||
}
|
||||
}
|
||||
|
||||
protected float mSymbolTimingGain = COARSE_TIMING_GAIN;
|
||||
protected SampleBuffer mSampleBuffer;
|
||||
protected ZeroCrossingErrorDetector mTimingErrorDetector = new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL);
|
||||
protected SynchronizationMonitor mSynchronizationMonitor;
|
||||
private IIRSinglePoleDCRemovalFilter mDCFilter = new IIRSinglePoleDCRemovalFilter(0.99999f);
|
||||
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
|
||||
private MessageFramer mMessageFramer;
|
||||
|
||||
private boolean mSampleDecision;
|
||||
|
||||
/**
|
||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder
|
||||
*
|
||||
* @param messageLength in symbols
|
||||
* Constructs an instance
|
||||
*/
|
||||
public LTRDecoder(int messageLength)
|
||||
public LTRDecoder()
|
||||
{
|
||||
this(messageLength, new SampleBuffer(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
|
||||
new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder.
|
||||
*
|
||||
* @param messageLength in symbols
|
||||
* @param sampleBuffer to use for storing sample decisions
|
||||
* @param detector for symbol timing errors
|
||||
* Registers the listener to receive decoded symbols.
|
||||
* @param listener to register
|
||||
*/
|
||||
public LTRDecoder(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector)
|
||||
public void setListener(Listener<boolean[]> listener)
|
||||
{
|
||||
mSynchronizationMonitor = new SynchronizationMonitor(messageLength);
|
||||
mSynchronizationMonitor.setListener(this);
|
||||
mTimingErrorDetector = detector;
|
||||
mSampleBuffer = sampleBuffer;
|
||||
mSampleBuffer.setTimingGain(mSymbolTimingGain);
|
||||
mSymbolListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the ISyncDetectedListener interface to be notified of message sync detection events.
|
||||
*
|
||||
* This allows the internal timing error detector to adjust symbol timing error gain levels according to the
|
||||
* message synchronization state to quickly adjust to initial signal streams or to reduce gain levels once
|
||||
* synchronization has been achieved.
|
||||
* Processes the demodulated 8 kHz audio samples to extract the 300-baud LTR signalling and delivers the decoded
|
||||
* symbol array to the registered listener.
|
||||
* @param samples to demodulate.
|
||||
*/
|
||||
@Override
|
||||
public void syncDetected(int bitErrors)
|
||||
public void receive(float[] samples)
|
||||
{
|
||||
mSynchronizationMonitor.syncDetected(bitErrors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncLost(int bitsProcessed)
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
|
||||
* greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
|
||||
* adjusted and then each of the symbols are decoded using a simple majority decision.
|
||||
*
|
||||
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
|
||||
*/
|
||||
@Override
|
||||
public void receive(float[] buffer)
|
||||
{
|
||||
float[] dcFiltered = mDCFilter.filter(buffer);
|
||||
float[] lowPassFiltered = mLowPassFilter.filter(dcFiltered);
|
||||
|
||||
for(float sample : lowPassFiltered)
|
||||
if(mSymbolListener != null)
|
||||
{
|
||||
mSampleDecision = sample > 0.0;
|
||||
float[] filtered = mLowPassFilter.filter(samples);
|
||||
|
||||
mSampleBuffer.receive(mSampleDecision);
|
||||
mTimingErrorDetector.receive(mSampleDecision);
|
||||
int timingAdjust = 0;
|
||||
int samplesToSkip = 0;
|
||||
|
||||
if(mSampleBuffer.hasSymbol())
|
||||
float[] buffer = new float[filtered.length + OVERLAP];
|
||||
boolean[] symbols = new boolean[(int)((filtered.length + mBaudCounter) / BAUD_LENGTH)];
|
||||
int symbolPointer = 0;
|
||||
float slope = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if(mMessageFramer != null)
|
||||
System.arraycopy(mResidual, 0, buffer, 0, mResidual.length);
|
||||
System.arraycopy(filtered, 0, buffer, mResidual.length, filtered.length);
|
||||
System.arraycopy(filtered, filtered.length - OVERLAP, mResidual, 0, OVERLAP);
|
||||
|
||||
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
|
||||
{
|
||||
mMessageFramer.process(mSampleBuffer.getSymbol());
|
||||
//Don't calculate slope if we're within the baud period following last symbol transition
|
||||
if(samplesToSkip > 0)
|
||||
{
|
||||
samplesToSkip--;
|
||||
}
|
||||
else
|
||||
{
|
||||
slope = calculateSlope(buffer, bufferPointer);
|
||||
|
||||
if(mSymbol)
|
||||
{
|
||||
//Loop for crest of slope at lowest point below the negative slope threshold for transition
|
||||
if(slope > mMaxSlope && mMaxSlope < -SLOPE_THRESHOLD)
|
||||
{
|
||||
mSymbol = false;
|
||||
|
||||
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 16 samples
|
||||
if(mBaudCounter < SYMBOL_TRANSITION_IDEAL_MIN)
|
||||
{
|
||||
timingAdjust = 1;
|
||||
}
|
||||
else if(mBaudCounter > SYMBOL_TRANSITION_IDEAL_MAX)
|
||||
{
|
||||
timingAdjust = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
timingAdjust = 0;
|
||||
}
|
||||
|
||||
mBaudCounter += timingAdjust;
|
||||
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
|
||||
}
|
||||
else if(slope < mMaxSlope)
|
||||
{
|
||||
mMaxSlope = slope;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Loop for crest of slope at the highest point above the positive threshold
|
||||
if(slope < mMaxSlope && mMaxSlope > SLOPE_THRESHOLD)
|
||||
{
|
||||
mSymbol = true;
|
||||
|
||||
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 16 samples
|
||||
if(mBaudCounter < SYMBOL_TRANSITION_IDEAL_MIN)
|
||||
{
|
||||
timingAdjust = 1;
|
||||
}
|
||||
else if(mBaudCounter > SYMBOL_TRANSITION_IDEAL_MAX)
|
||||
{
|
||||
timingAdjust = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
timingAdjust = 0;
|
||||
}
|
||||
mBaudCounter += timingAdjust;
|
||||
samplesToSkip = 26 + timingAdjust;
|
||||
}
|
||||
else if(slope > mMaxSlope)
|
||||
{
|
||||
mMaxSlope = slope;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mBaudCounter++;
|
||||
|
||||
if(mBaudCounter > BAUD_LENGTH)
|
||||
{
|
||||
//Expand symbols array length if needed to account for symbol timing adjustments
|
||||
if(symbolPointer >= symbols.length)
|
||||
{
|
||||
symbols = Arrays.copyOf(symbols, symbols.length + 1);
|
||||
}
|
||||
|
||||
symbols[symbolPointer++] = mSymbol;
|
||||
|
||||
mBaudCounter -= BAUD_LENGTH;
|
||||
|
||||
//Check for a continuous string of ones and flip the symbol state to zero.
|
||||
if(mSymbol)
|
||||
{
|
||||
mExcessiveOneSequenceCounter++;
|
||||
|
||||
if(mExcessiveOneSequenceCounter > MAX_ONES_SEQUENCE) //Verify that this is the right threshold for a string of ones
|
||||
{
|
||||
mSymbol = false;
|
||||
mMaxSlope = -1f;
|
||||
mExcessiveOneSequenceCounter = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mExcessiveOneSequenceCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.warn("Unexpected error while processing LTR samples", e);
|
||||
}
|
||||
|
||||
mSampleBuffer.resetAndAdjust(-mTimingErrorDetector.getError());
|
||||
|
||||
mSynchronizationMonitor.increment();
|
||||
//Truncate the symbols array if we didn't fill it completely, due to symbol timing adjustments.
|
||||
if(symbolPointer != symbols.length)
|
||||
{
|
||||
mSymbolListener.receive(Arrays.copyOf(symbols, symbolPointer));
|
||||
}
|
||||
else
|
||||
{
|
||||
mSymbolListener.receive(symbols);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to receive decoded LTR symbols.
|
||||
*
|
||||
* @param messageFramer to receive symbols.
|
||||
* Calculates the slope of the 27 samples in the values array starting at the specified offset.
|
||||
* @param samples to load from
|
||||
* @param offset to the sample start for the slope calculation
|
||||
*/
|
||||
public void setMessageFramer(MessageFramer messageFramer)
|
||||
public float calculateSlope(float[] samples, int offset)
|
||||
{
|
||||
mMessageFramer = messageFramer;
|
||||
}
|
||||
/** mean of accumulated x & y values, used in updating formulas */
|
||||
double xbar = 0;
|
||||
double ybar = samples[offset];
|
||||
|
||||
/**
|
||||
* Removes the symbol listener from receiving decoded LTR symbols.
|
||||
*/
|
||||
public void removeListener()
|
||||
{
|
||||
mMessageFramer = null;
|
||||
}
|
||||
double sumXY = 0;
|
||||
double fact1, dx, dy;
|
||||
|
||||
|
||||
/**
|
||||
* Implements the ISyncStateListener interface to receive synchronization state events and adjust the symbol timing
|
||||
* error gain levels on the internal timing error detector.
|
||||
*
|
||||
* Gain levels are defined relative to a unit gain of 1.0 over a number of symbol periods. LTR uses an initial
|
||||
* ramp-up symbol reversal pattern of 0101 which provides three zero crossing detection opportunities for the timing
|
||||
* error detector, therefore we react to a COARSE gain state to average timing error over 3 symbols.
|
||||
*
|
||||
* @param syncState from an external synchronization state monitor
|
||||
*/
|
||||
@Override
|
||||
public void setSyncState(SyncState syncState)
|
||||
{
|
||||
switch(syncState)
|
||||
for(int x = 1; x < SLOPE_CALCULATION_LENGTH; x++)
|
||||
{
|
||||
case FINE:
|
||||
mSymbolTimingGain = FINE_TIMING_GAIN;
|
||||
break;
|
||||
case MEDIUM:
|
||||
mSymbolTimingGain = MEDIUM_TIMING_GAIN;
|
||||
break;
|
||||
case COARSE:
|
||||
mSymbolTimingGain = COARSE_TIMING_GAIN;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized sync state level: " + syncState.name());
|
||||
fact1 = 1.0f + x;
|
||||
dx = x - xbar;
|
||||
dy = samples[offset + x] - ybar;
|
||||
sumXY += dx * dy * (x / (1.0f + x));
|
||||
xbar += dx / fact1;
|
||||
ybar += dy / fact1;
|
||||
}
|
||||
|
||||
mSampleBuffer.setTimingGain(mSymbolTimingGain);
|
||||
return (float)(sumXY / SLOPE_CALCULATION_SUM_XX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.fsk;
|
||||
|
||||
public class LTRDecoderInstrumented extends LTRDecoder
|
||||
{
|
||||
/**
|
||||
* Instrumented extension to LTRDecoder
|
||||
*
|
||||
* @param messageLength in symbols
|
||||
*/
|
||||
public LTRDecoderInstrumented(int messageLength)
|
||||
public LTRDecoderInstrumented()
|
||||
{
|
||||
super(messageLength, new SampleBufferInstrumented(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
|
||||
new ZeroCrossingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL));
|
||||
}
|
||||
|
||||
public SampleBufferInstrumented getSampleBuffer()
|
||||
{
|
||||
return (SampleBufferInstrumented)mSampleBuffer;
|
||||
}
|
||||
|
||||
public ZeroCrossingErrorDetectorInstrumented getErrorDetector()
|
||||
{
|
||||
return (ZeroCrossingErrorDetectorInstrumented)mTimingErrorDetector;
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -30,6 +30,7 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
||||
private IPhaseLockedLoop mPLL;
|
||||
private Complex mReceivedSample = new Complex(0, 0);
|
||||
private Listener<T> mSymbolListener;
|
||||
private boolean mRunning;
|
||||
|
||||
/**
|
||||
* Abstract Phase Shift Keyed (PSK) demodulator
|
||||
@@ -42,6 +43,31 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
||||
mPLL = phaseLockedLoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts this decoder and sets the running flag to true
|
||||
*/
|
||||
public void start()
|
||||
{
|
||||
mRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this decoder and sets the running flag to false.
|
||||
*/
|
||||
public void stop()
|
||||
{
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this demodulator is running and capable of processing samples
|
||||
* @return true if running
|
||||
*/
|
||||
public boolean isRunning()
|
||||
{
|
||||
return mRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive symbol decisions from this demodulator
|
||||
*/
|
||||
@@ -90,7 +116,10 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
||||
|
||||
for(int x = 0; x < i.length; x++)
|
||||
{
|
||||
receive(i[x], q[x]);
|
||||
if(isRunning())
|
||||
{
|
||||
receive(i[x], q[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||
|
||||
/**
|
||||
* Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
|
||||
* <p>
|
||||
* See ETSI TS 102 361-1 B.2.2.1 and B.2.2.2
|
||||
*/
|
||||
public class BPTC_16_2
|
||||
{
|
||||
private static final int[] DEINTERLEAVE = new int[]{0, 24, 1, 25, 2, 26, 3, 27, 4, 28, 5, 29, 6, 30, 7, 31, 8, 16,
|
||||
9, 17, 10, 18, 11, 19, 12, 20, 13, 21, 14, 22, 15, 23};
|
||||
|
||||
/**
|
||||
* Unscramble and perform FEC checks per paragraph B.2.2.1 for Non-Reverse Channel Single Burst
|
||||
*
|
||||
* @param message with 32 interleaved bits.
|
||||
* @return descrambled and error checked message or null if the process fails or there are too many errors.
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeShortBurst(CorrectedBinaryMessage message)
|
||||
{
|
||||
CorrectedBinaryMessage deinterleaved = deinterleave(message);
|
||||
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
|
||||
|
||||
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//Check for even parity. Bits 0-15 should be the same as bits 16-31.
|
||||
for(int x = 0; x < 16; x++)
|
||||
{
|
||||
if(deinterleaved.get(x) ^ deinterleaved.get(x + 16))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return deinterleaved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unscramble and perform FEC checks per paragraph B.2.2.2 for Reverse Channel Single Burst
|
||||
*
|
||||
* @param binaryMessage with 32 interleaved bits.
|
||||
* @return descrambled and error checked message or null if the process fails or there are too many errors.
|
||||
*/
|
||||
public static CorrectedBinaryMessage decodeReverseChannel(CorrectedBinaryMessage message)
|
||||
{
|
||||
CorrectedBinaryMessage deinterleaved = deinterleave(message);
|
||||
System.out.println(" DEINTER: " + deinterleaved.toHexString());
|
||||
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
|
||||
System.out.println(" DECODED: " + deinterleaved.toHexString());
|
||||
System.out.println("FEC:" + fec);
|
||||
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//Check for odd parity. Bits 0-15 should be opposite of bits 16-31.
|
||||
for(int x = 0; x < 16; x++)
|
||||
{
|
||||
if(deinterleaved.get(x) == deinterleaved.get(x + 16))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return deinterleaved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs deinterleave of the interleaved message.
|
||||
*
|
||||
* @param original to deinterleave
|
||||
* @return deinterleaved message
|
||||
*/
|
||||
public static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage original)
|
||||
{
|
||||
CorrectedBinaryMessage delinterleaved = new CorrectedBinaryMessage(32);
|
||||
for(int x = 0; x < 32; x++)
|
||||
{
|
||||
if(original.get(x))
|
||||
{
|
||||
delinterleaved.set(DEINTERLEAVE[x]);
|
||||
}
|
||||
}
|
||||
|
||||
return delinterleaved;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String[] msgs = new String[]{"05030A03", "35003A00", "1C6D2C9E"};
|
||||
|
||||
for(String msg : msgs)
|
||||
{
|
||||
CorrectedBinaryMessage original = new CorrectedBinaryMessage(BinaryMessage.loadHex(msg));
|
||||
System.out.println("ORIGINAL: " + original.toHexString());
|
||||
CorrectedBinaryMessage decoded = decodeReverseChannel(original);
|
||||
System.out.println("-------------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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.edac;
|
||||
|
||||
import io.github.dsheirer.bits.BinaryMessage;
|
||||
@@ -177,6 +180,28 @@ public class CRCDMR
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the residual CRC value using a mask of zero which should identify the mask value when an alternate
|
||||
* mask value is employed.
|
||||
* @param message to be checked
|
||||
* @param messageStart location
|
||||
* @param crcStart location
|
||||
* @return residual CRC check value.
|
||||
*/
|
||||
public static int calculateResidual(CorrectedBinaryMessage message, int messageStart, int crcStart)
|
||||
{
|
||||
int calculated = 0; //Starting value
|
||||
|
||||
/* Iterate the set bits and XOR running checksum with lookup value */
|
||||
for(int i = message.nextSetBit(messageStart); i >= messageStart && i < crcStart; i = message.nextSetBit(i + 1))
|
||||
{
|
||||
calculated ^= CCITT_80_CHECKSUMS[i - messageStart];
|
||||
}
|
||||
|
||||
int checksum = getIntChecksum(message, crcStart, 16);
|
||||
return calculated ^ checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error detection for CRC-9 protected Confirmed Packet Data blocks. These
|
||||
* data blocks have a slightly complicated structure because the checksum
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.edac;
|
||||
|
||||
import io.github.dsheirer.message.MessageDirection;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
@@ -98,6 +98,14 @@ public class CRCLTR
|
||||
|
||||
return CRC.FAILED_CRC;
|
||||
}
|
||||
|
||||
public static String getCRCReason(BitSet msg, MessageDirection direction)
|
||||
{
|
||||
int calculated = getCalculatedChecksum( msg );
|
||||
int transmitted = getTransmittedChecksum( msg );
|
||||
return "CALC: " + Integer.toHexString(calculated).toUpperCase() +
|
||||
" TRANS: " + Integer.toHexString(transmitted).toUpperCase();
|
||||
}
|
||||
|
||||
public static byte[] getChecks()
|
||||
{
|
||||
|
||||
@@ -1,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,61 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implements a message filter that passes (true) all messages
|
||||
* Implements an all-pass filter that automatically passes/allows any presented object.
|
||||
*
|
||||
* A key type of String is used, but that makes no difference in how this filter functions.
|
||||
*/
|
||||
@SuppressWarnings( "rawtypes" )
|
||||
public class AllPassFilter<T> extends Filter<T>
|
||||
public class AllPassFilter<T> extends Filter<T, String>
|
||||
{
|
||||
public AllPassFilter()
|
||||
private static final String KEY = "Other/Unlisted";
|
||||
private final AllPassKeyExtractor mKeyExtractor = new AllPassKeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param name to display for this filter
|
||||
*/
|
||||
public AllPassFilter(String name)
|
||||
{
|
||||
super(name);
|
||||
add(new FilterElement<>(KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function getKeyExtractor()
|
||||
{
|
||||
super( "Allow All Messages" );
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( T t )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( T t )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings( "unchecked" )
|
||||
public List getFilterElements()
|
||||
{
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
/**
|
||||
* Key extractor that always returns the same (String)key.
|
||||
*/
|
||||
public class AllPassKeyExtractor implements Function<T,String>
|
||||
{
|
||||
@Override
|
||||
public String apply(T t)
|
||||
{
|
||||
return KEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,206 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class Filter<T> implements IFilter<T>
|
||||
/**
|
||||
* Base filter class that uses an extractor function to extract a filter key (K) from an object of type T and then
|
||||
* lookup the filter element (rule) indicating if objects of this type are enabled.
|
||||
*
|
||||
* @param <T> object type that this filter can process.
|
||||
* @param <K> key type used to lookup filter elements
|
||||
*/
|
||||
public abstract class Filter<T,K> implements IFilter<T>
|
||||
{
|
||||
protected String mName;
|
||||
protected boolean mEnabled = true;
|
||||
|
||||
public Filter( String name )
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Filter.class);
|
||||
private String mName;
|
||||
private Map<K, FilterElement<K>> mFilterElementMap = new HashMap<>();
|
||||
private IFilterChangeListener mFilterChangeListener;
|
||||
|
||||
/**
|
||||
* Indicates if this filter is enabled or disabled
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param name of this filter
|
||||
*/
|
||||
public Filter(String name)
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables (true) or disables (false) this filter. An enabled filter will
|
||||
* evaluate messages and a disabled filter will return false on all message
|
||||
* test methods.
|
||||
*/
|
||||
public void setEnabled( boolean enabled )
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of filter elements managed by this filter
|
||||
*/
|
||||
public abstract List<FilterElement<?>> getFilterElements();
|
||||
/**
|
||||
* Indicates if this filter contains a filter element matching the key value.
|
||||
* @param key to check
|
||||
* @return true if this filter has a matching filter element.
|
||||
*/
|
||||
protected boolean hasKey(K key)
|
||||
{
|
||||
return mFilterElementMap.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this filter
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName( String name )
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
/**
|
||||
* Key extractor function provided by the subclass.
|
||||
* @return key extractor function
|
||||
*/
|
||||
public abstract Function<T,K> getKeyExtractor();
|
||||
|
||||
@Override
|
||||
public boolean passes(T t)
|
||||
{
|
||||
K key = getKeyExtractor().apply(t);
|
||||
|
||||
if(mFilterElementMap.containsKey(key))
|
||||
{
|
||||
return mFilterElementMap.get(key).isEnabled();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this filter can process the item T, meaning that it has a FilterElement that matches the key
|
||||
* value for items of type T.
|
||||
* @param t to test
|
||||
* @return true if this filter can process item t
|
||||
*/
|
||||
@Override
|
||||
public boolean canProcess(T t)
|
||||
{
|
||||
return hasKey(getKeyExtractor().apply(t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled()
|
||||
{
|
||||
for(FilterElement filterElement: mFilterElementMap.values())
|
||||
{
|
||||
if(filterElement.isEnabled())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the child filter element to this filter
|
||||
* @param filterElement to add
|
||||
*/
|
||||
public void add(FilterElement<K> filterElement)
|
||||
{
|
||||
if(mFilterElementMap.containsKey(filterElement.getElement()))
|
||||
{
|
||||
LOG.warn("Filter element for key [" + filterElement.getElement() + "] named [" + filterElement.getName() +
|
||||
"] already exists - overwriting existing value");
|
||||
}
|
||||
|
||||
mFilterElementMap.put(filterElement.getElement(), filterElement);
|
||||
filterElement.register(mFilterChangeListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* List of filter elements managed by this filter
|
||||
*/
|
||||
public final List<FilterElement<K>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<>(mFilterElementMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this filter
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this filter
|
||||
*
|
||||
* @param name to set
|
||||
*/
|
||||
public void setName(String name)
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty version or name of this filter.
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of enabled child filter elements.
|
||||
*
|
||||
* @return count
|
||||
*/
|
||||
@Override
|
||||
public int getEnabledCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for(FilterElement filterElement : mFilterElementMap.values())
|
||||
{
|
||||
if(filterElement.isEnabled())
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of child filter elements
|
||||
*
|
||||
* @return count
|
||||
*/
|
||||
@Override
|
||||
public int getElementCount()
|
||||
{
|
||||
return mFilterElementMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a filter change listener on this filter and all filter elements.
|
||||
* @param listener to be notified of any filter changes.
|
||||
*/
|
||||
@Override
|
||||
public void register(IFilterChangeListener listener)
|
||||
{
|
||||
mFilterChangeListener = listener;
|
||||
|
||||
for(FilterElement child: mFilterElementMap.values())
|
||||
{
|
||||
child.register(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
71
src/main/java/io/github/dsheirer/filter/FilterEditor.java
Normal file
71
src/main/java/io/github/dsheirer/filter/FilterEditor.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
import java.awt.Component;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
/**
|
||||
* Filter editor
|
||||
* @param <T> item type for editing
|
||||
*/
|
||||
public class FilterEditor<T> extends JFrame
|
||||
{
|
||||
private FilterEditorPanel<T> mEditorPanel;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param title for the editor window frame
|
||||
* @param owner to register the popup location
|
||||
* @param filterSet to use initially
|
||||
*/
|
||||
public FilterEditor(String title, Component owner, FilterSet<T> filterSet)
|
||||
{
|
||||
if(filterSet == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Unable to construct FilterEditor - FilterSet cannot be null");
|
||||
}
|
||||
setTitle(title);
|
||||
setSize(600, 400);
|
||||
setLocationRelativeTo(owner);
|
||||
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][]"));
|
||||
mEditorPanel = new FilterEditorPanel<>(filterSet);
|
||||
JScrollPane scroller = new JScrollPane(mEditorPanel);
|
||||
scroller.setViewportView(mEditorPanel);
|
||||
add(scroller, "wrap");
|
||||
JButton close = new JButton("Close");
|
||||
close.addActionListener(e -> dispose());
|
||||
add(close);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this editor with the filterset.
|
||||
* @param filterSet to use in this editor.
|
||||
*/
|
||||
public void updateFilterSet(FilterSet<T> filterSet)
|
||||
{
|
||||
mEditorPanel.updateFilterSet(filterSet);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +1,128 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.CellEditorListener;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeCellRenderer;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeCellEditor;
|
||||
import javax.swing.tree.TreeNode;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Editor panel for managing the state of a filter set.
|
||||
*
|
||||
* @param <T> element type for the filter set.
|
||||
*/
|
||||
public class FilterEditorPanel<T> extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private JTree mTree;
|
||||
private DefaultTreeModel mModel;
|
||||
private FilterSet<T> mFilterSet;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param filterSet to manage
|
||||
*/
|
||||
public FilterEditorPanel(FilterSet<T> filterSet)
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
|
||||
mFilterSet = filterSet;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the panel and the tree model.
|
||||
*/
|
||||
private void init()
|
||||
{
|
||||
DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet);
|
||||
|
||||
mModel = new DefaultTreeModel(root);
|
||||
|
||||
addFilterSet(mFilterSet, root);
|
||||
|
||||
mTree = new JTree(mModel);
|
||||
mTree.setShowsRootHandles(true);
|
||||
|
||||
mTree.addMouseListener(new MouseHandler());
|
||||
|
||||
mTree.getSelectionModel().setSelectionMode(
|
||||
TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
|
||||
mTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
mTree.setCellRenderer(new EditorTreeCellRenderer());
|
||||
|
||||
mTree.setCellEditor(new FilterTreeNodeEditor());
|
||||
|
||||
mTree.setCellEditor(new FilterTreeCellEditor());
|
||||
mTree.setEditable(true);
|
||||
|
||||
add(mTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the filter set for this editor panel
|
||||
* @param filterSet to use
|
||||
*/
|
||||
public void updateFilterSet(FilterSet<T> filterSet)
|
||||
{
|
||||
remove(mTree);
|
||||
mFilterSet = filterSet;
|
||||
init();
|
||||
revalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the filter set as a child tree node to the parent.
|
||||
* @param filterSet to add to the tree
|
||||
* @param parent node for the filter set child tree node.
|
||||
*/
|
||||
private void addFilterSet(FilterSet<T> filterSet, DefaultMutableTreeNode parent)
|
||||
{
|
||||
List<IFilter<T>> filters = filterSet.getFilters();
|
||||
|
||||
/* sort the filters in alphabetical order by name */
|
||||
filters.sort(Comparator.comparing(IFilter::getName));
|
||||
|
||||
for(IFilter<T> filter : filters)
|
||||
{
|
||||
DefaultMutableTreeNode child = new DefaultMutableTreeNode(filter);
|
||||
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(filter);
|
||||
mModel.insertNodeInto(childNode, parent, parent.getChildCount());
|
||||
|
||||
mModel.insertNodeInto(child, parent, parent.getChildCount());
|
||||
|
||||
if(filter instanceof FilterSet)
|
||||
if(filter instanceof FilterSet childFilterSet)
|
||||
{
|
||||
addFilterSet((FilterSet<T>) filter, child);
|
||||
addFilterSet(childFilterSet, childNode);
|
||||
}
|
||||
else if(filter instanceof Filter)
|
||||
else if(filter instanceof Filter childFilter)
|
||||
{
|
||||
addFilter((Filter<T>) filter, child);
|
||||
addFilter(childFilter, childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addFilter(Filter<T> filter, DefaultMutableTreeNode parent)
|
||||
/**
|
||||
* Adds the filter as a child tree node to the parent.
|
||||
*
|
||||
* @param filter to add
|
||||
* @param parent tree node
|
||||
*/
|
||||
private void addFilter(Filter filter, DefaultMutableTreeNode parent)
|
||||
{
|
||||
List<FilterElement<?>> elements = filter.getFilterElements();
|
||||
|
||||
@@ -103,123 +131,39 @@ public class FilterEditorPanel<T> extends JPanel
|
||||
for(FilterElement<?> element : elements)
|
||||
{
|
||||
DefaultMutableTreeNode child = new DefaultMutableTreeNode(element);
|
||||
|
||||
mModel.insertNodeInto(child, parent, parent.getChildCount());
|
||||
}
|
||||
}
|
||||
|
||||
public class MouseHandler implements MouseListener
|
||||
/**
|
||||
* Accesses the tree's root tree node.
|
||||
* @return root node.
|
||||
*/
|
||||
private DefaultMutableTreeNode getRoot()
|
||||
{
|
||||
public MouseHandler()
|
||||
if(mTree.getModel().getRoot() instanceof DefaultMutableTreeNode root)
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent event)
|
||||
{
|
||||
if(SwingUtilities.isRightMouseButton(event))
|
||||
{
|
||||
int row = mTree.getRowForLocation(event.getX(),
|
||||
event.getY());
|
||||
|
||||
if(row != -1)
|
||||
{
|
||||
mTree.setSelectionRow(row);
|
||||
|
||||
Object selectedNode =
|
||||
mTree.getLastSelectedPathComponent();
|
||||
|
||||
if(selectedNode instanceof DefaultMutableTreeNode)
|
||||
{
|
||||
final DefaultMutableTreeNode node =
|
||||
(DefaultMutableTreeNode) selectedNode;
|
||||
|
||||
if(node.getChildCount() > 0)
|
||||
{
|
||||
JPopupMenu selectionMenu = new JPopupMenu();
|
||||
|
||||
JMenuItem selectAll = new JMenuItem("Select All");
|
||||
selectAll.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
setFilterEnabled(mModel, node, true);
|
||||
}
|
||||
});
|
||||
|
||||
selectionMenu.add(selectAll);
|
||||
|
||||
JMenuItem deselectAll = new JMenuItem("Deselect All");
|
||||
deselectAll.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
{
|
||||
setFilterEnabled(mModel, node, false);
|
||||
}
|
||||
});
|
||||
|
||||
selectionMenu.add(deselectAll);
|
||||
|
||||
selectionMenu.show(mTree,
|
||||
event.getX(),
|
||||
event.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e)
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively sets the enabled state on all filter node children below the
|
||||
* specified parent node.
|
||||
* Recursively sets the enabled state on all filter node children below the specified parent node.
|
||||
*
|
||||
* @param model - model containing the tree nodes
|
||||
* @param node - parent node
|
||||
* @param enabled - true or false to enable or disable the filters
|
||||
*/
|
||||
private void setFilterEnabled(DefaultTreeModel model,
|
||||
DefaultMutableTreeNode node,
|
||||
boolean enabled)
|
||||
private void setFilterEnabled(DefaultTreeModel model, DefaultMutableTreeNode node, boolean enabled)
|
||||
{
|
||||
Object obj = node.getUserObject();
|
||||
|
||||
if(obj instanceof FilterSet<?>)
|
||||
if(obj instanceof FilterElement filterElement)
|
||||
{
|
||||
((FilterSet<?>) obj).setEnabled(enabled);
|
||||
filterElement.setEnabled(enabled);
|
||||
}
|
||||
else if(obj instanceof Filter)
|
||||
{
|
||||
((Filter<?>) obj).setEnabled(enabled);
|
||||
}
|
||||
else if(obj instanceof FilterElement)
|
||||
{
|
||||
((FilterElement<?>) obj).setEnabled(enabled);
|
||||
}
|
||||
|
||||
model.nodeChanged(node);
|
||||
|
||||
/* Recursively set the children of this node */
|
||||
Enumeration<?> children = node.children();
|
||||
@@ -228,43 +172,123 @@ public class FilterEditorPanel<T> extends JPanel
|
||||
{
|
||||
Object child = children.nextElement();
|
||||
|
||||
if(child instanceof DefaultMutableTreeNode)
|
||||
if(child instanceof DefaultMutableTreeNode childNode)
|
||||
{
|
||||
setFilterEnabled(model, (DefaultMutableTreeNode) child, enabled);
|
||||
setFilterEnabled(model, childNode, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
model.nodeChanged(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches the tree to find the tree node containing the specified filter element.
|
||||
*
|
||||
* @param node to start search
|
||||
* @param element to find
|
||||
*/
|
||||
private DefaultMutableTreeNode findFilterElementNode(DefaultMutableTreeNode node, FilterElement<?> element)
|
||||
{
|
||||
if(node != null)
|
||||
{
|
||||
if(node.getUserObject() instanceof FilterElement<?> selfFilterElement && selfFilterElement.equals(element))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
for(int x = 0; x < node.getChildCount(); x++)
|
||||
{
|
||||
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
|
||||
{
|
||||
DefaultMutableTreeNode found = findFilterElementNode(child, element);
|
||||
|
||||
if(found != null)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the display value for the specified node and all parents in the nodes parentage.
|
||||
* @param node to update
|
||||
*/
|
||||
private void updateParentage(DefaultMutableTreeNode node)
|
||||
{
|
||||
mModel.nodeChanged(node);
|
||||
|
||||
TreeNode parent = node.getParent();
|
||||
|
||||
while(parent != null)
|
||||
{
|
||||
mModel.nodeChanged(parent);
|
||||
parent = parent.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches the tree to find the tree node containing the specified filter.
|
||||
*
|
||||
* @param node to start search
|
||||
* @param filter to find
|
||||
*/
|
||||
private DefaultMutableTreeNode findFilterNode(DefaultMutableTreeNode node, IFilter<?> filter)
|
||||
{
|
||||
if(node != null)
|
||||
{
|
||||
if(node.getUserObject() instanceof IFilter<?> selfFilter && selfFilter.equals(filter))
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
for(int x = 0; x < node.getChildCount(); x++)
|
||||
{
|
||||
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
|
||||
{
|
||||
DefaultMutableTreeNode found = findFilterNode(child, filter);
|
||||
|
||||
if(found != null)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Can't evaluate - node is null");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom cell renderer
|
||||
*/
|
||||
public class EditorTreeCellRenderer extends DefaultTreeCellRenderer
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public Component getTreeCellRendererComponent(JTree tree,
|
||||
Object treeNode,
|
||||
boolean selected,
|
||||
boolean expanded,
|
||||
boolean leaf,
|
||||
int row,
|
||||
boolean hasFocus)
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object treeNode, boolean selected, boolean expanded,
|
||||
boolean leaf, int row, boolean hasFocus)
|
||||
{
|
||||
if(treeNode instanceof DefaultMutableTreeNode)
|
||||
{
|
||||
Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject();
|
||||
|
||||
JCheckBox checkBox = null;
|
||||
|
||||
if(userObject instanceof IFilter)
|
||||
if(userObject instanceof IFilter filter)
|
||||
{
|
||||
IFilter<?> filter = (IFilter<?>) userObject;
|
||||
|
||||
checkBox = new JCheckBox(filter.getName());
|
||||
checkBox = new FilterCheckBox(filter);
|
||||
checkBox.setSelected(filter.isEnabled());
|
||||
}
|
||||
else if(userObject instanceof FilterElement)
|
||||
else if(userObject instanceof FilterElement element)
|
||||
{
|
||||
FilterElement<?> element = (FilterElement<?>) userObject;
|
||||
|
||||
checkBox = new JCheckBox(element.getName());
|
||||
checkBox = new FilterElementCheckBox(element);
|
||||
checkBox.setSelected(element.isEnabled());
|
||||
}
|
||||
|
||||
@@ -285,95 +309,110 @@ public class FilterEditorPanel<T> extends JPanel
|
||||
}
|
||||
}
|
||||
|
||||
return super.getTreeCellRendererComponent(tree, treeNode, selected,
|
||||
expanded, leaf, row, hasFocus);
|
||||
return super.getTreeCellRendererComponent(tree, treeNode, selected, expanded, leaf, row, hasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check box used in filter tree nodes
|
||||
*/
|
||||
public class FilterCheckBox extends JCheckBox
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private IFilter<?> mFilter;
|
||||
|
||||
private IFilter<T> mFilter;
|
||||
|
||||
public FilterCheckBox(IFilter<T> filter)
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param filter
|
||||
*/
|
||||
public FilterCheckBox(IFilter<?> filter)
|
||||
{
|
||||
super(filter.getName());
|
||||
|
||||
mFilter = filter;
|
||||
|
||||
setSelected(mFilter.isEnabled());
|
||||
addItemListener(e -> {
|
||||
DefaultMutableTreeNode selfNode = findFilterNode(getRoot(), mFilter);
|
||||
|
||||
addItemListener(new ItemListener()
|
||||
{
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e)
|
||||
if(selfNode != null)
|
||||
{
|
||||
mFilter.setEnabled(FilterCheckBox.this.isSelected());
|
||||
setFilterEnabled(mModel, selfNode, FilterCheckBox.this.isSelected());
|
||||
updateLabel();
|
||||
updateParentage(selfNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(boolean b)
|
||||
{
|
||||
super.setSelected(b);
|
||||
updateLabel();
|
||||
}
|
||||
|
||||
public void updateLabel()
|
||||
{
|
||||
setText(mFilter.getName() + " (" + mFilter.getEnabledCount() + "/" + mFilter.getElementCount() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check box used in filter element tree nodes
|
||||
*/
|
||||
public class FilterElementCheckBox extends JCheckBox
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private FilterElement<?> mFilter;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param filter element
|
||||
*/
|
||||
public FilterElementCheckBox(FilterElement<?> filter)
|
||||
{
|
||||
super(filter.getName());
|
||||
|
||||
mFilter = filter;
|
||||
|
||||
setSelected(mFilter.isEnabled());
|
||||
|
||||
addItemListener(new ItemListener()
|
||||
{
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent arg0)
|
||||
addItemListener(arg0 -> {
|
||||
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
|
||||
DefaultMutableTreeNode self = findFilterElementNode(getRoot(), mFilter);
|
||||
if(self != null)
|
||||
{
|
||||
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
|
||||
updateParentage(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class FilterTreeNodeEditor implements TreeCellEditor
|
||||
/**
|
||||
* Editor for filter tree nodes
|
||||
*/
|
||||
public class FilterTreeCellEditor implements TreeCellEditor
|
||||
{
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public Component getTreeCellEditorComponent(JTree tree, Object node,
|
||||
boolean isSelected, boolean expanded, boolean leaf, int row)
|
||||
public Component getTreeCellEditorComponent(JTree tree, Object node, boolean isSelected, boolean expanded, boolean leaf, int row)
|
||||
{
|
||||
if(node instanceof DefaultMutableTreeNode)
|
||||
if(node instanceof DefaultMutableTreeNode mutableTreeNode)
|
||||
{
|
||||
Object userObject = ((DefaultMutableTreeNode) node).getUserObject();
|
||||
Object userObject = mutableTreeNode.getUserObject();
|
||||
|
||||
if(userObject instanceof IFilter)
|
||||
if(userObject instanceof IFilter filter)
|
||||
{
|
||||
return new FilterCheckBox((IFilter) userObject);
|
||||
return new FilterCheckBox(filter);
|
||||
}
|
||||
else if(userObject instanceof FilterElement)
|
||||
else if(userObject instanceof FilterElement filterElement)
|
||||
{
|
||||
return new FilterElementCheckBox((FilterElement<?>) userObject);
|
||||
return new FilterElementCheckBox(filterElement);
|
||||
}
|
||||
}
|
||||
|
||||
return new JLabel(node.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCellEditorListener(CellEditorListener l)
|
||||
{
|
||||
}
|
||||
public void addCellEditorListener(CellEditorListener l) {}
|
||||
|
||||
@Override
|
||||
public void cancelCellEditing()
|
||||
{
|
||||
|
||||
}
|
||||
public void cancelCellEditing() {}
|
||||
|
||||
@Override
|
||||
public Object getCellEditorValue()
|
||||
@@ -388,9 +427,7 @@ public class FilterEditorPanel<T> extends JPanel
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCellEditorListener(CellEditorListener l)
|
||||
{
|
||||
}
|
||||
public void removeCellEditorListener(CellEditorListener l) {}
|
||||
|
||||
@Override
|
||||
public boolean shouldSelectCell(EventObject anEvent)
|
||||
|
||||
@@ -1,55 +1,148 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
/**
|
||||
* Filter element provides filtering and enabled state tracking for an individual filterable element.
|
||||
*
|
||||
* @param <T> element to track and filter.
|
||||
*/
|
||||
public class FilterElement<T> implements Comparable<FilterElement<T>>
|
||||
{
|
||||
private boolean mEnabled;
|
||||
private T mElement;
|
||||
|
||||
public FilterElement( T element, boolean enabled )
|
||||
{
|
||||
mElement = element;
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
public FilterElement( T t )
|
||||
{
|
||||
this( t, true );
|
||||
}
|
||||
|
||||
public T getElement()
|
||||
{
|
||||
return mElement;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return getName();
|
||||
}
|
||||
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
public void setEnabled( boolean enabled )
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return mElement.toString();
|
||||
}
|
||||
private boolean mEnabled;
|
||||
private final T mElement;
|
||||
private IFilterChangeListener mFilterChangeListener;
|
||||
|
||||
@Override
|
||||
public int compareTo( FilterElement<T> other )
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param element to track
|
||||
* @param enabled enabled state of this filter element
|
||||
*/
|
||||
public FilterElement(T element, boolean enabled)
|
||||
{
|
||||
return toString().compareTo( other.toString() );
|
||||
mElement = element;
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance with a default state of enabled (true).
|
||||
*
|
||||
* @param t element to track
|
||||
*/
|
||||
public FilterElement(T t)
|
||||
{
|
||||
this(t, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener with each child filter element.
|
||||
*
|
||||
* @param listener to register
|
||||
*/
|
||||
public void register(IFilterChangeListener listener)
|
||||
{
|
||||
mFilterChangeListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element being filtered.
|
||||
*
|
||||
* @return element
|
||||
*/
|
||||
public T getElement()
|
||||
{
|
||||
return mElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name or display string for the element.
|
||||
*
|
||||
* @return string version of the element.
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this filter is enabled indicating that elements of this type will pass this filter.
|
||||
*
|
||||
* @return enabled state.
|
||||
*/
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enabled state of this filter.
|
||||
*
|
||||
* @param enabled true to allow this filter's element type to pass the filter.
|
||||
*/
|
||||
public void setEnabled(boolean enabled)
|
||||
{
|
||||
mEnabled = enabled;
|
||||
|
||||
if(mFilterChangeListener != null)
|
||||
{
|
||||
mFilterChangeListener.filterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the tracked element.
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return mElement.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements compareto interface for ordering of filter elements.
|
||||
*
|
||||
* @param other the object to be compared.
|
||||
* @return comparison value
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof FilterElement)) return false;
|
||||
return compareTo((FilterElement) o) == 0;
|
||||
public int compareTo(FilterElement<T> other)
|
||||
{
|
||||
return toString().compareTo(other.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement equals interface for comparison of filter elements.
|
||||
*
|
||||
* @param o to compare
|
||||
* @return comparison
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if(o instanceof FilterElement fe)
|
||||
{
|
||||
return compareTo(fe) == 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +1,239 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
import io.github.dsheirer.log.LoggingSuppressor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Filter set is a parent container for filters.
|
||||
*
|
||||
* @param <T> type of element tracked/filtered by this filter set.
|
||||
*/
|
||||
public class FilterSet<T> implements IFilter<T>
|
||||
{
|
||||
protected List<IFilter<T>> mFilters = new ArrayList<IFilter<T>>();
|
||||
protected boolean mEnabled = true;
|
||||
protected String mName;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FilterSet.class);
|
||||
private static LoggingSuppressor sLoggingSuppressor = new LoggingSuppressor(LOGGER);
|
||||
private List<IFilter<T>> mFilters = new ArrayList<>();
|
||||
private String mName;
|
||||
private IFilterChangeListener mFilterChangeListener;
|
||||
|
||||
public FilterSet( String name )
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public FilterSet()
|
||||
{
|
||||
mName = "Message Filter";
|
||||
}
|
||||
|
||||
public FilterSet(IFilter<T> filter)
|
||||
/**
|
||||
* Constructs an instance of a filter set.
|
||||
*
|
||||
* @param name of this filter set.
|
||||
*/
|
||||
public FilterSet(String name)
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance with a single filter using the default 'Filters' name.
|
||||
*
|
||||
* @param filter for this filter set.
|
||||
*/
|
||||
public FilterSet(IFilter<T> filter)
|
||||
{
|
||||
this("Filters");
|
||||
addFilter(filter);
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName( String name )
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes( T t )
|
||||
{
|
||||
if( mEnabled )
|
||||
{
|
||||
for( IFilter<T> filter: mFilters )
|
||||
{
|
||||
// Make sure to loop through all filters.
|
||||
// Only return if true or all filters have been evaluated.
|
||||
if( filter.canProcess( t ) && filter.passes( t ))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess( T t )
|
||||
{
|
||||
if( mEnabled )
|
||||
/**
|
||||
* Registers the listener with each child filter element.
|
||||
* @param listener to register
|
||||
*/
|
||||
public void register(IFilterChangeListener listener)
|
||||
{
|
||||
mFilterChangeListener = listener;
|
||||
|
||||
for(IFilter child: mFilters)
|
||||
{
|
||||
for( IFilter<T> filter: mFilters )
|
||||
child.register(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this filter set.
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this filter set.
|
||||
*
|
||||
* @param name to set
|
||||
*/
|
||||
public void setName(String name)
|
||||
{
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name for this filter set.
|
||||
*
|
||||
* @return name
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the element passes at least one of the child filters in this filter set.
|
||||
*
|
||||
* @param t to evaluate
|
||||
* @return true if the argument passes a child filter.
|
||||
*/
|
||||
@Override
|
||||
public boolean passes(T t)
|
||||
{
|
||||
for(IFilter<T> filter : mFilters)
|
||||
{
|
||||
//Stop at the first filter that says that it can process the message type.
|
||||
if(filter.canProcess(t))
|
||||
{
|
||||
if( filter.canProcess( t ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return filter.passes(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this filter set contains at least one filter that can evaluate the type of argument that is presented.
|
||||
*
|
||||
* @param t element to evaluate
|
||||
* @return true if this filter set can evaluate objects of the argument's type.
|
||||
*/
|
||||
@Override
|
||||
public boolean canProcess(T t)
|
||||
{
|
||||
for(IFilter<T> filter : mFilters)
|
||||
{
|
||||
if(filter.canProcess(t))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
sLoggingSuppressor.info(t.getClass().getSimpleName(), 1, "FilterSet [" +
|
||||
this.getClass().getSimpleName() + "] has no filter element for items of type [" +
|
||||
t.getClass().getSimpleName() + "]");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Child filters
|
||||
*
|
||||
* @return filters
|
||||
*/
|
||||
public List<IFilter<T>> getFilters()
|
||||
{
|
||||
return mFilters;
|
||||
}
|
||||
|
||||
public void addFilters( List<IFilter<T>> filters )
|
||||
{
|
||||
mFilters.addAll( filters );
|
||||
}
|
||||
|
||||
public void addFilter( IFilter<T> filter )
|
||||
{
|
||||
mFilters.add( filter );
|
||||
}
|
||||
|
||||
public void removeFilter( IFilter<T> filter )
|
||||
{
|
||||
mFilters.remove( filter );
|
||||
return new ArrayList<>(mFilters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
/**
|
||||
* Adds a list of child filters to this filter set.
|
||||
* @param filters to add
|
||||
*/
|
||||
public void addFilters(List<IFilter<T>> filters)
|
||||
{
|
||||
for(IFilter<T> filter: filters)
|
||||
{
|
||||
addFilter(filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the child filter to this filter set.
|
||||
* @param filter to add
|
||||
*/
|
||||
public void addFilter(IFilter<T> filter)
|
||||
{
|
||||
if(filter instanceof Filter<?,?> filterInstance && filterInstance.getKeyExtractor() == null)
|
||||
{
|
||||
LOGGER.warn("Filter [" + filter.getClass() + "] has a null key extractor");
|
||||
}
|
||||
|
||||
mFilters.add(filter);
|
||||
filter.register(mFilterChangeListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if any of the child filters contain at least one filter element that is enabled.
|
||||
* @return enabled state
|
||||
*/
|
||||
@Override
|
||||
public void setEnabled( boolean enabled )
|
||||
{
|
||||
mEnabled = enabled;
|
||||
}
|
||||
public boolean isEnabled()
|
||||
{
|
||||
for(IFilter filter: mFilters)
|
||||
{
|
||||
if(filter.isEnabled())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive count of enabled child filter elements.
|
||||
* @return count
|
||||
*/
|
||||
@Override
|
||||
public int getEnabledCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for(IFilter filter: mFilters)
|
||||
{
|
||||
count += filter.getEnabledCount();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive count of child filter elements.
|
||||
* @return count
|
||||
*/
|
||||
@Override
|
||||
public int getElementCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for(IFilter filter: mFilters)
|
||||
{
|
||||
count += filter.getElementCount();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,75 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
/**
|
||||
* Filter interface
|
||||
*
|
||||
* @param <T> type of filter
|
||||
*/
|
||||
public interface IFilter<T>
|
||||
{
|
||||
/**
|
||||
* Generic filter method.
|
||||
*
|
||||
* @param message - message to filter
|
||||
* @return - true if the message passes the filter
|
||||
*/
|
||||
public boolean passes( T t );
|
||||
/**
|
||||
* Generic filter method.
|
||||
*
|
||||
* @param t - message to filter
|
||||
* @return - true if the message passes the filter
|
||||
*/
|
||||
boolean passes(T t);
|
||||
|
||||
/**
|
||||
* Indicates if the filter can process (filter) the object
|
||||
*
|
||||
* @param message - candidate message for filtering
|
||||
*
|
||||
* @return - true if the filter is capable of filtering the message
|
||||
*/
|
||||
public boolean canProcess( T t );
|
||||
/**
|
||||
* Indicates if the filter can process (filter) the object
|
||||
*
|
||||
* @param t - candidate message for filtering
|
||||
* @return - true if the filter is capable of filtering the message
|
||||
*/
|
||||
boolean canProcess(T t);
|
||||
|
||||
/**
|
||||
* Indicates if this filter is enabled to evaluate messages
|
||||
*/
|
||||
public boolean isEnabled();
|
||||
/**
|
||||
* Indicates if this filter is enabled to evaluate messages
|
||||
*/
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Sets the enabled state of the filter
|
||||
*/
|
||||
public void setEnabled( boolean enabled );
|
||||
|
||||
/**
|
||||
* Display name for the filter
|
||||
*/
|
||||
public String getName();
|
||||
/**
|
||||
* Display name for the filter
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* (Recursive) count of enabled child filter elements.
|
||||
*
|
||||
* @return total enable count.
|
||||
*/
|
||||
int getEnabledCount();
|
||||
|
||||
/**
|
||||
* (Recursive) count of total child filter elements.
|
||||
*
|
||||
* @return total count of child filter elements.
|
||||
*/
|
||||
int getElementCount();
|
||||
|
||||
/**
|
||||
* Registers the listener to be notified when the enabled state of any child filter elements is updated.
|
||||
*
|
||||
* @param listener to be notified
|
||||
*/
|
||||
void register(IFilterChangeListener listener);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
public interface IFilterChangeListener
|
||||
{
|
||||
void filterChanged();
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,30 +14,36 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.p25.phase1.message.filter;
|
||||
package io.github.dsheirer.filter;
|
||||
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.SyncLossMessage;
|
||||
import java.util.function.Function;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SyncLossMessageFilter extends Filter<IMessage>
|
||||
/**
|
||||
* Filter for sync-loss messages
|
||||
*/
|
||||
public class SyncLossMessageFilter extends Filter<IMessage,String>
|
||||
{
|
||||
private static final String SYNC_LOSS_KEY = "Sync-Loss";
|
||||
private KeyExtractor mKeyExtractor = new KeyExtractor();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public SyncLossMessageFilter()
|
||||
{
|
||||
super("Sync Loss");
|
||||
super("Sync Loss Messages");
|
||||
add(new FilterElement<>(SYNC_LOSS_KEY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IMessage message)
|
||||
public Function<IMessage, String> getKeyExtractor()
|
||||
{
|
||||
return mEnabled && canProcess(message);
|
||||
return mKeyExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,9 +52,20 @@ public class SyncLossMessageFilter extends Filter<IMessage>
|
||||
return message instanceof SyncLossMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
/**
|
||||
* Key extractor
|
||||
*/
|
||||
private class KeyExtractor implements Function<IMessage,String>
|
||||
{
|
||||
return Collections.EMPTY_LIST;
|
||||
@Override
|
||||
public String apply(IMessage message)
|
||||
{
|
||||
if(message instanceof SyncLossMessage)
|
||||
{
|
||||
return SYNC_LOSS_KEY;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.controller.channel.map.ChannelMap;
|
||||
import io.github.dsheirer.controller.channel.map.ChannelRange;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.gui.dmr.DMRRecordingViewer;
|
||||
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
|
||||
import io.github.dsheirer.gui.icon.IconManager;
|
||||
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
||||
import io.github.dsheirer.gui.playlist.PlaylistEditor;
|
||||
@@ -37,10 +35,14 @@ import io.github.dsheirer.gui.preference.PreferenceEditorType;
|
||||
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
|
||||
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
||||
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
||||
import io.github.dsheirer.gui.viewer.RecordingViewer;
|
||||
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.jmbe.JmbeEditor;
|
||||
import io.github.dsheirer.jmbe.JmbeEditorRequest;
|
||||
import io.github.dsheirer.module.log.EventLogManager;
|
||||
import io.github.dsheirer.monitor.ResourceMonitor;
|
||||
import io.github.dsheirer.monitor.StatusBox;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
@@ -67,7 +69,7 @@ public class JavaFxWindowManager extends Application
|
||||
public static final String USER_PREFERENCES_EDITOR = "preferences";
|
||||
public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog";
|
||||
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
|
||||
public static final String STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER = "dmr.message.viewer";
|
||||
public static final String STAGE_MONITOR_KEY_RECORDING_VIEWER = "recording.viewer";
|
||||
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
|
||||
public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor";
|
||||
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
|
||||
@@ -82,14 +84,15 @@ public class JavaFxWindowManager extends Application
|
||||
private TunerManager mTunerManager;
|
||||
private UserPreferences mUserPreferences;
|
||||
private UserPreferencesEditor mUserPreferencesEditor;
|
||||
private DMRRecordingViewer mDmrRecordingViewer;
|
||||
private RecordingViewer mRecordingViewer;
|
||||
|
||||
private Stage mChannelMapStage;
|
||||
private Stage mIconManagerStage;
|
||||
private Stage mJmbeEditorStage;
|
||||
private Stage mPlaylistStage;
|
||||
private Stage mUserPreferencesStage;
|
||||
private Stage mDmrRecordingViewerStage;
|
||||
private Stage mRecordingViewerStage;
|
||||
private JFXPanel mStatusPanel;
|
||||
|
||||
/**
|
||||
* Constructs an instance. Note: this constructor is used for Swing applications.
|
||||
@@ -118,6 +121,27 @@ public class JavaFxWindowManager extends Application
|
||||
setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or accesses the JavaFX status panel, used by the main application GUI.
|
||||
* @param resourceMonitor for statistics
|
||||
* @return JFXPanel accessible on Swing thread that delegates JavaFX scene creation to the FX event thread.
|
||||
*/
|
||||
public JFXPanel getStatusPanel(ResourceMonitor resourceMonitor)
|
||||
{
|
||||
if(mStatusPanel == null)
|
||||
{
|
||||
mStatusPanel = new JFXPanel();
|
||||
|
||||
//JFXPanel has to be populated on the FX event thread
|
||||
Platform.runLater(() -> {
|
||||
Scene scene = new Scene(new StatusBox(resourceMonitor));
|
||||
mStatusPanel.setScene(scene);
|
||||
});
|
||||
}
|
||||
|
||||
return mStatusPanel;
|
||||
}
|
||||
|
||||
private void setup()
|
||||
{
|
||||
//Register this class to receive events via each method annotated with @Subscribe
|
||||
@@ -164,27 +188,7 @@ public class JavaFxWindowManager extends Application
|
||||
public void shutdown()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
|
||||
if(mChannelMapStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mChannelMapStage);
|
||||
}
|
||||
|
||||
if(mIconManagerStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mIconManagerStage);
|
||||
}
|
||||
|
||||
if(mPlaylistStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mPlaylistStage);
|
||||
}
|
||||
|
||||
if(mUserPreferencesStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mUserPreferencesStage);
|
||||
}
|
||||
|
||||
mUserPreferences.getJavaFxPreferences().clearStageMonitors();
|
||||
Platform.exit();
|
||||
}
|
||||
|
||||
@@ -195,31 +199,31 @@ public class JavaFxWindowManager extends Application
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage for the DMR Message Viewer
|
||||
* Stage for the recording viewer
|
||||
*/
|
||||
public Stage getDmrRecordingViewerStage()
|
||||
public Stage getRecordingViewerStage()
|
||||
{
|
||||
if(mDmrRecordingViewerStage == null)
|
||||
if(mRecordingViewerStage == null)
|
||||
{
|
||||
createJFXPanel();
|
||||
Scene scene = new Scene(getDmrRecordingViewer(), 1100, 800);
|
||||
mDmrRecordingViewerStage = new Stage();
|
||||
mDmrRecordingViewerStage.setTitle("sdrtrunk - DMR Recording Viewer");
|
||||
mDmrRecordingViewerStage.setScene(scene);
|
||||
mUserPreferences.getJavaFxPreferences().monitor(mDmrRecordingViewerStage, STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER);
|
||||
Scene scene = new Scene(getRecordingViewer(), 1100, 800);
|
||||
mRecordingViewerStage = new Stage();
|
||||
mRecordingViewerStage.setTitle("sdrtrunk - Message Recording Viewer (.bits)");
|
||||
mRecordingViewerStage.setScene(scene);
|
||||
mUserPreferences.getJavaFxPreferences().monitor(mRecordingViewerStage, STAGE_MONITOR_KEY_RECORDING_VIEWER);
|
||||
}
|
||||
|
||||
return mDmrRecordingViewerStage;
|
||||
return mRecordingViewerStage;
|
||||
}
|
||||
|
||||
public DMRRecordingViewer getDmrRecordingViewer()
|
||||
public RecordingViewer getRecordingViewer()
|
||||
{
|
||||
if(mDmrRecordingViewer == null)
|
||||
if(mRecordingViewer == null)
|
||||
{
|
||||
mDmrRecordingViewer = new DMRRecordingViewer();
|
||||
mRecordingViewer = new RecordingViewer();
|
||||
}
|
||||
|
||||
return mDmrRecordingViewer;
|
||||
return mRecordingViewer;
|
||||
}
|
||||
|
||||
public Stage getIconManagerStage()
|
||||
@@ -264,9 +268,7 @@ public class JavaFxWindowManager extends Application
|
||||
else
|
||||
{
|
||||
execute(() -> {
|
||||
getJmbeEditorStage().show();
|
||||
getJmbeEditorStage().requestFocus();
|
||||
getJmbeEditorStage().toFront();
|
||||
restoreStage(getJmbeEditorStage());
|
||||
getJmbeEditor().process(request);
|
||||
});
|
||||
}
|
||||
@@ -337,9 +339,7 @@ public class JavaFxWindowManager extends Application
|
||||
execute(() -> {
|
||||
try
|
||||
{
|
||||
getPlaylistStage().show();
|
||||
getPlaylistStage().requestFocus();
|
||||
getPlaylistStage().toFront();
|
||||
restoreStage(getPlaylistStage());
|
||||
getPlaylistEditor().process(request);
|
||||
}
|
||||
catch(Throwable t)
|
||||
@@ -387,9 +387,7 @@ public class JavaFxWindowManager extends Application
|
||||
public void process(final ViewUserPreferenceEditorRequest request)
|
||||
{
|
||||
execute(() -> {
|
||||
getUserPreferencesStage().show();
|
||||
getUserPreferencesStage().requestFocus();
|
||||
getUserPreferencesStage().toFront();
|
||||
restoreStage(getUserPreferencesStage());
|
||||
getUserPreferencesEditor().process(request);
|
||||
});
|
||||
}
|
||||
@@ -428,11 +426,7 @@ public class JavaFxWindowManager extends Application
|
||||
@Subscribe
|
||||
public void process(final ViewIconManagerRequest request)
|
||||
{
|
||||
execute(() -> {
|
||||
getIconManagerStage().show();
|
||||
getIconManagerStage().requestFocus();
|
||||
getIconManagerStage().toFront();
|
||||
});
|
||||
execute(() -> restoreStage(getIconManagerStage()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,9 +436,7 @@ public class JavaFxWindowManager extends Application
|
||||
public void process(final ViewChannelMapEditorRequest request)
|
||||
{
|
||||
execute(() -> {
|
||||
getChannelMapStage().show();
|
||||
getChannelMapStage().requestFocus();
|
||||
getChannelMapStage().toFront();
|
||||
restoreStage(getChannelMapStage());
|
||||
getChannelMapEditor().process(request);
|
||||
});
|
||||
}
|
||||
@@ -453,13 +445,24 @@ public class JavaFxWindowManager extends Application
|
||||
* Process a channel map editor request
|
||||
*/
|
||||
@Subscribe
|
||||
public void process(final ViewDmrRecordingViewerRequest request)
|
||||
public void process(final ViewRecordingViewerRequest request)
|
||||
{
|
||||
execute(() -> {
|
||||
getDmrRecordingViewerStage().show();
|
||||
getDmrRecordingViewerStage().requestFocus();
|
||||
getDmrRecordingViewerStage().toFront();
|
||||
});
|
||||
execute(() -> restoreStage(getRecordingViewerStage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the stage to previous size and location.
|
||||
* @param stage to restore.
|
||||
*/
|
||||
private void restoreStage(Stage stage)
|
||||
{
|
||||
stage.setIconified(false);
|
||||
stage.show();
|
||||
stage.requestFocus();
|
||||
stage.toFront();
|
||||
stage.setMinHeight(stage.getHeight());
|
||||
stage.setMinWidth(stage.getWidth());
|
||||
// System.out.println("Stage [" + stage.getTitle() + "] restored - width[" + stage.getWidth() + "] height [" + stage.getHeight() + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,17 +32,18 @@ import io.github.dsheirer.controller.channel.ChannelAutoStartFrame;
|
||||
import io.github.dsheirer.controller.channel.ChannelException;
|
||||
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
|
||||
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
||||
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
||||
import io.github.dsheirer.gui.preference.CalibrateRequest;
|
||||
import io.github.dsheirer.gui.preference.PreferenceEditorType;
|
||||
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
|
||||
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
|
||||
import io.github.dsheirer.gui.viewer.ViewRecordingViewerRequest;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.log.ApplicationLog;
|
||||
import io.github.dsheirer.map.MapService;
|
||||
import io.github.dsheirer.module.log.EventLogManager;
|
||||
import io.github.dsheirer.monitor.ResourceMonitor;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
@@ -54,7 +55,7 @@ import io.github.dsheirer.source.tuner.TunerEvent;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerSpectralDisplayManager;
|
||||
import io.github.dsheirer.spectrum.ClearTunerMenuItem;
|
||||
import io.github.dsheirer.spectrum.DisableSpectrumWaterfallMenuItem;
|
||||
import io.github.dsheirer.spectrum.ShowTunerMenuItem;
|
||||
import io.github.dsheirer.spectrum.SpectralDisplayPanel;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
@@ -68,11 +69,11 @@ import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Point;
|
||||
import java.awt.Robot;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
@@ -80,7 +81,9 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.prefs.Preferences;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import jiconfont.icons.font_awesome.FontAwesome;
|
||||
import jiconfont.swing.IconFontSwing;
|
||||
@@ -105,14 +108,19 @@ import javax.swing.plaf.metal.MetalLookAndFeel;
|
||||
public class SDRTrunk implements Listener<TunerEvent>
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(SDRTrunk.class);
|
||||
private Preferences mPreferences = Preferences.userNodeForPackage(SDRTrunk.class);
|
||||
|
||||
private static final String PROPERTY_BROADCAST_STATUS_VISIBLE = "main.broadcast.status.visible";
|
||||
private static final String PREFERENCE_BROADCAST_STATUS_VISIBLE = "sdrtrunk.broadcast.status.visible";
|
||||
private static final String PREFERENCE_NOW_PLAYING_DETAILS_VISIBLE = "sdrtrunk.now.playing.details.visible";
|
||||
private static final String PREFERENCE_RESOURCE_STATUS_VISIBLE = "sdrtrunk.resource.status.visible";
|
||||
private static final String BASE_WINDOW_NAME = "sdrtrunk.main.window";
|
||||
private static final String CONTROLLER_PANEL_IDENTIFIER = BASE_WINDOW_NAME + ".control.panel";
|
||||
private static final String SPECTRAL_PANEL_IDENTIFIER = BASE_WINDOW_NAME + ".spectral.panel";
|
||||
private static final String WINDOW_FRAME_IDENTIFIER = BASE_WINDOW_NAME + ".frame";
|
||||
|
||||
private boolean mBroadcastStatusVisible;
|
||||
private boolean mResourceStatusVisible;
|
||||
private boolean mNowPlayingDetailsVisible;
|
||||
private AudioRecordingManager mAudioRecordingManager;
|
||||
private AudioStreamingManager mAudioStreamingManager;
|
||||
private BroadcastStatusPanel mBroadcastStatusPanel;
|
||||
@@ -127,6 +135,8 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
private UserPreferences mUserPreferences = new UserPreferences();
|
||||
private TunerManager mTunerManager;
|
||||
private ApplicationLog mApplicationLog;
|
||||
private ResourceMonitor mResourceMonitor;
|
||||
private JFXPanel mResourceStatusPanel;
|
||||
|
||||
private String mTitle;
|
||||
|
||||
@@ -140,6 +150,8 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
mApplicationLog = new ApplicationLog(mUserPreferences);
|
||||
mApplicationLog.start();
|
||||
|
||||
mResourceMonitor = new ResourceMonitor(mUserPreferences);
|
||||
|
||||
String operatingSystem = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
|
||||
|
||||
if(operatingSystem.contains("mac") || operatingSystem.contains("nux"))
|
||||
@@ -155,18 +167,10 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
}
|
||||
}
|
||||
|
||||
//Setup the application home directory
|
||||
Path home = getHomePath();
|
||||
|
||||
ThreadPool.logSettings();
|
||||
|
||||
mLog.info("Home path: " + home.toString());
|
||||
|
||||
//Load properties file
|
||||
if(home != null)
|
||||
{
|
||||
loadProperties(home);
|
||||
}
|
||||
loadProperties();
|
||||
|
||||
//Log current properties setting
|
||||
SystemProperties.getInstance().logCurrentSettings();
|
||||
@@ -213,10 +217,12 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
MapService mapService = new MapService(mIconModel);
|
||||
mPlaylistManager.getChannelProcessingManager().addDecodeEventListener(mapService);
|
||||
|
||||
mNowPlayingDetailsVisible = mPreferences.getBoolean(PREFERENCE_NOW_PLAYING_DETAILS_VISIBLE, true);
|
||||
|
||||
if(!GraphicsEnvironment.isHeadless())
|
||||
{
|
||||
mControllerPanel = new ControllerPanel(mPlaylistManager, audioPlaybackManager, mIconModel, mapService,
|
||||
mSettingsManager, mTunerManager, mUserPreferences);
|
||||
mSettingsManager, mTunerManager, mUserPreferences, mNowPlayingDetailsVisible);
|
||||
}
|
||||
|
||||
mSpectralPanel = new SpectralDisplayPanel(mPlaylistManager, mSettingsManager, mTunerManager.getDiscoveredTunerModel());
|
||||
@@ -323,7 +329,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
*/
|
||||
private void initGUI()
|
||||
{
|
||||
mMainGui.setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
|
||||
mMainGui.setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]0[shrink 0]"));
|
||||
|
||||
/**
|
||||
* Setup main JFrame window
|
||||
@@ -379,7 +385,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
mSplitPane.add(mSpectralPanel);
|
||||
mSplitPane.add(mControllerPanel);
|
||||
|
||||
mBroadcastStatusVisible = SystemProperties.getInstance().get(PROPERTY_BROADCAST_STATUS_VISIBLE, false);
|
||||
mBroadcastStatusVisible = mPreferences.getBoolean(PREFERENCE_BROADCAST_STATUS_VISIBLE, false);
|
||||
|
||||
//Show broadcast status panel when user requests - disabled by default
|
||||
if(mBroadcastStatusVisible)
|
||||
@@ -389,6 +395,13 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
|
||||
mMainGui.add(mSplitPane, "cell 0 0,span,grow");
|
||||
|
||||
mResourceMonitor.start();
|
||||
mResourceStatusVisible = mPreferences.getBoolean(PREFERENCE_RESOURCE_STATUS_VISIBLE, true);
|
||||
if(mResourceStatusVisible)
|
||||
{
|
||||
mMainGui.add(getResourceStatusPanel(), "span,growx");
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu items
|
||||
*/
|
||||
@@ -399,14 +412,9 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
menuBar.add(fileMenu);
|
||||
|
||||
JMenuItem exitMenu = new JMenuItem("Exit");
|
||||
exitMenu.addActionListener(
|
||||
new ActionListener()
|
||||
{
|
||||
public void actionPerformed(ActionEvent event)
|
||||
{
|
||||
processShutdown();
|
||||
System.exit(0);
|
||||
}
|
||||
exitMenu.addActionListener(event -> {
|
||||
processShutdown();
|
||||
System.exit(0);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -415,56 +423,121 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
JMenu viewMenu = new JMenu("View");
|
||||
|
||||
JMenuItem viewPlaylistItem = new JMenuItem("Playlist Editor");
|
||||
viewPlaylistItem.setIcon(IconFontSwing.buildIcon(FontAwesome.PLAY_CIRCLE_O, 12));
|
||||
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
|
||||
viewMenu.add(viewPlaylistItem);
|
||||
|
||||
viewMenu.add(new JSeparator());
|
||||
|
||||
JMenuItem dmrMessageViewerMenu = new JMenuItem("DMR Recording Viewer");
|
||||
dmrMessageViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewDmrRecordingViewerRequest()));
|
||||
viewMenu.add(dmrMessageViewerMenu);
|
||||
|
||||
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
||||
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||
viewMenu.add(settingsMenu);
|
||||
|
||||
JMenuItem logFilesMenu = new JMenuItem("Logs & Recordings");
|
||||
logFilesMenu.addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0)
|
||||
JMenuItem viewApplicationLogsMenu = new JMenuItem("Application Log Files");
|
||||
viewApplicationLogsMenu.setIcon(IconFontSwing.buildIcon(FontAwesome.FOLDER_OPEN_O, 12));
|
||||
viewApplicationLogsMenu.addActionListener(arg0 -> {
|
||||
File logsDirectory = mUserPreferences.getDirectoryPreference().getDirectoryApplicationLog().toFile();
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().open(getHomePath().toFile());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't open file explorer");
|
||||
Desktop.getDesktop().open(logsDirectory);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't open file explorer");
|
||||
|
||||
JOptionPane.showMessageDialog(mMainGui,
|
||||
"Can't launch file explorer - files are located at: " +
|
||||
getHomePath().toString(),
|
||||
"Can't launch file explorer",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
JOptionPane.showMessageDialog(mMainGui,
|
||||
"Can't launch file explorer - files are located at: " + logsDirectory,
|
||||
"Can't launch file explorer",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
viewMenu.add(logFilesMenu);
|
||||
viewMenu.add(viewApplicationLogsMenu);
|
||||
|
||||
JMenuItem viewRecordingsMenuItem = new JMenuItem("Audio Recordings");
|
||||
viewRecordingsMenuItem.setIcon(IconFontSwing.buildIcon(FontAwesome.FOLDER_OPEN_O, 12));
|
||||
viewRecordingsMenuItem.addActionListener(arg0 -> {
|
||||
File recordingsDirectory = mUserPreferences.getDirectoryPreference().getDirectoryRecording().toFile();
|
||||
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().open(recordingsDirectory);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't open file explorer");
|
||||
|
||||
JOptionPane.showMessageDialog(mMainGui,
|
||||
"Can't launch file explorer - files are located at: " +
|
||||
recordingsDirectory,
|
||||
"Can't launch file explorer",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
viewMenu.add(viewRecordingsMenuItem);
|
||||
|
||||
JMenuItem viewEventLogsMenu = new JMenuItem("Channel Event Log Files");
|
||||
viewEventLogsMenu.setIcon(IconFontSwing.buildIcon(FontAwesome.FOLDER_OPEN_O, 12));
|
||||
viewEventLogsMenu.addActionListener(arg0 -> {
|
||||
File eventLogsDirectory = mUserPreferences.getDirectoryPreference().getDirectoryEventLog().toFile();
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().open(eventLogsDirectory);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't open file explorer");
|
||||
|
||||
JOptionPane.showMessageDialog(mMainGui,
|
||||
"Can't launch file explorer - files are located at: " + eventLogsDirectory,
|
||||
"Can't launch file explorer",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
viewMenu.add(viewEventLogsMenu);
|
||||
|
||||
JMenuItem iconManagerMenu = new JMenuItem("Icon Manager");
|
||||
iconManagerMenu.setIcon(IconFontSwing.buildIcon(FontAwesome.PICTURE_O, 12));
|
||||
iconManagerMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||
viewMenu.add(iconManagerMenu);
|
||||
|
||||
JMenuItem recordingViewerMenu = new JMenuItem("Message Recording Viewer (.bits)");
|
||||
recordingViewerMenu.setIcon(IconFontSwing.buildIcon(FontAwesome.BRAILLE, 12));
|
||||
recordingViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewRecordingViewerRequest()));
|
||||
viewMenu.add(recordingViewerMenu);
|
||||
|
||||
JMenuItem viewScreenCapturesMenu = new JMenuItem("Screen Captures");
|
||||
viewScreenCapturesMenu.setIcon(IconFontSwing.buildIcon(FontAwesome.FOLDER_OPEN_O, 12));
|
||||
viewScreenCapturesMenu.addActionListener(arg0 -> {
|
||||
File screenCapturesDirectory = mUserPreferences.getDirectoryPreference().getDirectoryScreenCapture().toFile();
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().open(screenCapturesDirectory);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Couldn't open file explorer");
|
||||
|
||||
JOptionPane.showMessageDialog(mMainGui,
|
||||
"Can't launch file explorer - files are located at: " + screenCapturesDirectory,
|
||||
"Can't launch file explorer",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
viewMenu.add(viewScreenCapturesMenu);
|
||||
|
||||
JMenuItem preferencesItem = new JMenuItem("User Preferences");
|
||||
preferencesItem.setIcon(IconFontSwing.buildIcon(FontAwesome.COG, 12));
|
||||
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
|
||||
viewMenu.add(preferencesItem);
|
||||
|
||||
viewMenu.add(new JSeparator());
|
||||
viewMenu.add(new TunersMenu());
|
||||
viewMenu.add(new JSeparator());
|
||||
viewMenu.add(new ClearTunerMenuItem(mSpectralPanel));
|
||||
viewMenu.add(new BroadcastStatusVisibleMenuItem(mControllerPanel));
|
||||
viewMenu.add(new DisableSpectrumWaterfallMenuItem(mSpectralPanel));
|
||||
viewMenu.add(new NowPlayingChannelDetailsVisibleMenuItem());
|
||||
viewMenu.add(new BroadcastStatusVisibleMenuItem());
|
||||
viewMenu.add(new ResourceStatusVisibleMenuItem());
|
||||
|
||||
menuBar.add(viewMenu);
|
||||
|
||||
JMenuItem screenCaptureItem = new JMenuItem("Screen Capture");
|
||||
screenCaptureItem.setIcon(IconFontSwing.buildIcon(FontAwesome.CAMERA, 12));
|
||||
screenCaptureItem.setMnemonic(KeyEvent.VK_C);
|
||||
screenCaptureItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.ALT_MASK));
|
||||
screenCaptureItem.setMaximumSize(screenCaptureItem.getPreferredSize());
|
||||
@@ -515,6 +588,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
mLog.info("Stopping channels ...");
|
||||
mPlaylistManager.getChannelProcessingManager().shutdown();
|
||||
mAudioRecordingManager.stop();
|
||||
mResourceMonitor.stop();
|
||||
|
||||
mLog.info("Stopping spectral display ...");
|
||||
mSpectralPanel.clearTuner();
|
||||
@@ -560,7 +634,54 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
mMainGui.revalidate();
|
||||
});
|
||||
|
||||
SystemProperties.getInstance().set(PROPERTY_BROADCAST_STATUS_VISIBLE, mBroadcastStatusVisible);
|
||||
mPreferences.putBoolean(PREFERENCE_BROADCAST_STATUS_VISIBLE, mBroadcastStatusVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy constructor for resource status panel
|
||||
*/
|
||||
private JFXPanel getResourceStatusPanel()
|
||||
{
|
||||
|
||||
if(mResourceStatusPanel == null)
|
||||
{
|
||||
mResourceStatusPanel = mJavaFxWindowManager.getStatusPanel(mResourceMonitor);
|
||||
}
|
||||
|
||||
return mResourceStatusPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles visibility of the resource status panel at the bottom of the main UI window
|
||||
*/
|
||||
private void toggleResourceStatusPanelVisibility()
|
||||
{
|
||||
mResourceStatusVisible = !mResourceStatusVisible;
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
if(mResourceStatusVisible)
|
||||
{
|
||||
mMainGui.add(getResourceStatusPanel(), "span,growx");
|
||||
}
|
||||
else
|
||||
{
|
||||
mMainGui.remove(getResourceStatusPanel());
|
||||
}
|
||||
|
||||
mMainGui.revalidate();
|
||||
});
|
||||
|
||||
mPreferences.putBoolean(PREFERENCE_RESOURCE_STATUS_VISIBLE, mResourceStatusVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles visibility of the Now Playing channel details panel
|
||||
*/
|
||||
private void toggleNowPlayingDetailsPanelVisibility()
|
||||
{
|
||||
mNowPlayingDetailsVisible = !mNowPlayingDetailsVisible;
|
||||
mControllerPanel.getNowPlayingPanel().setDetailTabsVisible(mNowPlayingDetailsVisible);
|
||||
mPreferences.putBoolean(PREFERENCE_NOW_PLAYING_DETAILS_VISIBLE, mNowPlayingDetailsVisible);
|
||||
}
|
||||
|
||||
|
||||
@@ -568,34 +689,30 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
* Loads the application properties file from the user's home directory,
|
||||
* creating the properties file for the first-time, if necessary
|
||||
*/
|
||||
private void loadProperties(Path homePath)
|
||||
private void loadProperties()
|
||||
{
|
||||
Path propsPath = homePath.resolve("SDRTrunk.properties");
|
||||
Path propertiesPath = mUserPreferences.getDirectoryPreference().getDirectoryApplicationRoot().resolve("SDRTrunk.properties");
|
||||
|
||||
if(!Files.exists(propsPath))
|
||||
if(!Files.exists(propertiesPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
mLog.info("SDRTrunk - creating application properties file [" +
|
||||
propsPath.toAbsolutePath() + "]");
|
||||
|
||||
Files.createFile(propsPath);
|
||||
mLog.info("SDRTrunk - creating application properties file [" + propertiesPath.toAbsolutePath() + "]");
|
||||
Files.createFile(propertiesPath);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
mLog.error("SDRTrunk - couldn't create application properties "
|
||||
+ "file [" + propsPath.toAbsolutePath(), e);
|
||||
mLog.error("SDRTrunk - couldn't create application properties file [" + propertiesPath.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if(Files.exists(propsPath))
|
||||
if(Files.exists(propertiesPath))
|
||||
{
|
||||
SystemProperties.getInstance().load(propsPath);
|
||||
SystemProperties.getInstance().load(propertiesPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("SDRTrunk - couldn't find or recreate the SDRTrunk " +
|
||||
"application properties file");
|
||||
mLog.error("SDRTrunk - couldn't find or recreate the SDRTrunk application properties file");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,26 +795,50 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast status panel visible toggle menu item
|
||||
*/
|
||||
public class BroadcastStatusVisibleMenuItem extends JCheckBoxMenuItem
|
||||
{
|
||||
private ControllerPanel mControllerPanel;
|
||||
|
||||
public BroadcastStatusVisibleMenuItem(ControllerPanel controllerPanel)
|
||||
public BroadcastStatusVisibleMenuItem()
|
||||
{
|
||||
super("Show Streaming Status");
|
||||
setSelected(mBroadcastStatusVisible);
|
||||
addActionListener(e -> {
|
||||
toggleBroadcastStatusPanelVisibility();
|
||||
setSelected(mBroadcastStatusVisible);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mControllerPanel = controllerPanel;
|
||||
/**
|
||||
* Resource status panel visible toggle menu item
|
||||
*/
|
||||
public class ResourceStatusVisibleMenuItem extends JCheckBoxMenuItem
|
||||
{
|
||||
public ResourceStatusVisibleMenuItem()
|
||||
{
|
||||
super("Show Resource Status");
|
||||
setSelected(mResourceStatusVisible);
|
||||
addActionListener(e -> {
|
||||
toggleResourceStatusPanelVisibility();
|
||||
setSelected(mResourceStatusVisible);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(mBroadcastStatusPanel != null);
|
||||
|
||||
addActionListener(new ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
toggleBroadcastStatusPanelVisibility();
|
||||
setSelected(mBroadcastStatusVisible);
|
||||
}
|
||||
/**
|
||||
* Now Playing channel details visible toggle menu item
|
||||
*/
|
||||
public class NowPlayingChannelDetailsVisibleMenuItem extends JCheckBoxMenuItem
|
||||
{
|
||||
public NowPlayingChannelDetailsVisibleMenuItem()
|
||||
{
|
||||
super("Show Now Playing Channel Details");
|
||||
setSelected(mNowPlayingDetailsVisible);
|
||||
addActionListener(e -> {
|
||||
toggleNowPlayingDetailsPanelVisibility();
|
||||
setSelected(mNowPlayingDetailsVisible);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,29 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.control;
|
||||
|
||||
import java.util.function.UnaryOperator;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.util.converter.IntegerStringConverter;
|
||||
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* Text field control for integer values.
|
||||
*/
|
||||
@@ -52,7 +48,7 @@ public class IntegerTextField extends TextField
|
||||
return null;
|
||||
};
|
||||
|
||||
setTextFormatter(new TextFormatter<Integer>(new IntegerStringConverter(), 0, filter));
|
||||
setTextFormatter(new TextFormatter<Integer>(new IntegerStringConverter(), null, filter));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +67,7 @@ public class IntegerTextField extends TextField
|
||||
//Do nothing ... we couldn't parse the value
|
||||
}
|
||||
|
||||
return null;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||
@@ -58,7 +61,7 @@ public class LTRNetSampleBufferChart extends LineChart
|
||||
|
||||
mLTRNetDecoderInstrumented = decoder;
|
||||
decoder.bufferCount.addListener(new BufferChangeListener());
|
||||
decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
}
|
||||
|
||||
public void setBuffer(boolean[] buffer)
|
||||
@@ -89,7 +92,7 @@ public class LTRNetSampleBufferChart extends LineChart
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
|
||||
// setBuffer(mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLine());
|
||||
|
||||
while(mSymbol.size() < 2)
|
||||
{
|
||||
@@ -115,29 +118,29 @@ public class LTRNetSampleBufferChart extends LineChart
|
||||
mSymbolSamples.add(sample);
|
||||
}
|
||||
|
||||
int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
|
||||
mSamplePoints.get(0).setXValue(pointer1);
|
||||
mSamplePoints.get(1).setXValue(pointer2);
|
||||
// mSamplePoints.get(0).setXValue(pointer1);
|
||||
// mSamplePoints.get(1).setXValue(pointer2);
|
||||
|
||||
float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
|
||||
float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
|
||||
// float samplesRemaining = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getMidSymbolSamplingPoint();
|
||||
// float samplesPerSymbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSamplesPerSymbol();
|
||||
|
||||
float start = pointer1 + samplesRemaining;
|
||||
float end = start + samplesPerSymbol;
|
||||
// float start = pointer1 + samplesRemaining;
|
||||
// float end = start + samplesPerSymbol;
|
||||
|
||||
mSymbolSamples.get(0).setXValue(start);
|
||||
mSymbolSamples.get(1).setXValue(end);
|
||||
// mSymbolSamples.get(0).setXValue(start);
|
||||
// mSymbolSamples.get(1).setXValue(end);
|
||||
|
||||
boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
|
||||
int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
|
||||
int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
|
||||
// boolean symbol = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getLastSymbol();
|
||||
// int symbolStart = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolStart();
|
||||
// int symbolEnd = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getSymbolEnd();
|
||||
|
||||
mSymbol.get(0).setXValue(symbolStart);
|
||||
mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
|
||||
mSymbol.get(1).setXValue(symbolEnd);
|
||||
mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
|
||||
// mSymbol.get(0).setXValue(symbolStart);
|
||||
// mSymbol.get(0).setYValue(symbol ? 0.55f : -0.55f);
|
||||
// mSymbol.get(1).setXValue(symbolEnd);
|
||||
// mSymbol.get(1).setYValue(symbol ? 0.55f : -0.55f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,20 +155,20 @@ public class LTRNetSampleBufferChart extends LineChart
|
||||
mZeroCrossing.add(sample);
|
||||
}
|
||||
|
||||
int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
// int pointer1 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLinePointer();
|
||||
// int pointer2 = mLTRNetDecoderInstrumented.getLTRDecoder().getSampleBuffer().getDelayLineSecondPointer();
|
||||
|
||||
float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
|
||||
float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
|
||||
|
||||
int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
|
||||
reference--;
|
||||
|
||||
float start = reference - zeroCrossingIdeal;
|
||||
float end = reference - detectedZeroCrossing;
|
||||
|
||||
mZeroCrossing.get(0).setXValue(start);
|
||||
mZeroCrossing.get(1).setXValue(end);
|
||||
// float zeroCrossingIdeal = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal();
|
||||
// float detectedZeroCrossing = zeroCrossingIdeal - ((Number)newValue).floatValue();
|
||||
//
|
||||
// int reference = (zeroCrossingIdeal < pointer1 || detectedZeroCrossing < pointer1 ? pointer2 : pointer1);
|
||||
// reference--;
|
||||
//
|
||||
// float start = reference - zeroCrossingIdeal;
|
||||
// float end = reference - detectedZeroCrossing;
|
||||
//
|
||||
// mZeroCrossing.get(0).setXValue(start);
|
||||
// mZeroCrossing.get(1).setXValue(end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.chart;
|
||||
|
||||
import io.github.dsheirer.module.decode.ltrnet.LTRNetDecoderInstrumented;
|
||||
@@ -53,7 +56,7 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
||||
setData(observableList);
|
||||
|
||||
mLTRNetDecoderInstrumented = decoder;
|
||||
decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
// decoder.getLTRDecoder().getErrorDetector().timingError.addListener(new ErrorChangeListener());
|
||||
decoder.bufferCount.addListener(new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
@@ -84,13 +87,13 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
||||
|
||||
private void updateBuffer()
|
||||
{
|
||||
boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
Data<Number,Number> sample = mCurrentSamples.get(x);
|
||||
sample.setYValue(samples[x] ? 0.7f : 0.3f);
|
||||
}
|
||||
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
//
|
||||
// for(int x = 0; x < samples.length; x++)
|
||||
// {
|
||||
// Data<Number,Number> sample = mCurrentSamples.get(x);
|
||||
// sample.setYValue(samples[x] ? 0.7f : 0.3f);
|
||||
// }
|
||||
}
|
||||
|
||||
public class ErrorChangeListener implements ChangeListener
|
||||
@@ -98,35 +101,35 @@ public class ZeroCrossingErrorDetectorChart extends LineChart
|
||||
@Override
|
||||
public void changed(ObservableValue observable, Object oldValue, Object newValue)
|
||||
{
|
||||
float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
|
||||
|
||||
boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
Data<Number,Number> sample = mPreviousSamples.get(x);
|
||||
sample.setYValue(samples[x] ? -0.4f : -0.6f);
|
||||
}
|
||||
|
||||
Data<Number,Number> ideal1 = mIdeal.get(0);
|
||||
ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
Data<Number,Number> ideal2 = mIdeal.get(1);
|
||||
ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
|
||||
if(error == 0.0f)
|
||||
{
|
||||
Data<Number,Number> detected1 = mDetected.get(0);
|
||||
detected1.setXValue(0.0f);
|
||||
Data<Number,Number> detected2 = mDetected.get(1);
|
||||
detected2.setXValue(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Data<Number,Number> detected1 = mDetected.get(0);
|
||||
detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
Data<Number,Number> detected2 = mDetected.get(1);
|
||||
detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
}
|
||||
// float error = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getError();
|
||||
//
|
||||
// boolean[] samples = mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getBuffer();
|
||||
//
|
||||
// for(int x = 0; x < samples.length; x++)
|
||||
// {
|
||||
// Data<Number,Number> sample = mPreviousSamples.get(x);
|
||||
// sample.setYValue(samples[x] ? -0.4f : -0.6f);
|
||||
// }
|
||||
//
|
||||
// Data<Number,Number> ideal1 = mIdeal.get(0);
|
||||
// ideal1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
// Data<Number,Number> ideal2 = mIdeal.get(1);
|
||||
// ideal2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getZeroCrossingIdeal());
|
||||
//
|
||||
// if(error == 0.0f)
|
||||
// {
|
||||
// Data<Number,Number> detected1 = mDetected.get(0);
|
||||
// detected1.setXValue(0.0f);
|
||||
// Data<Number,Number> detected2 = mDetected.get(1);
|
||||
// detected2.setXValue(0.0f);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Data<Number,Number> detected1 = mDetected.get(0);
|
||||
// detected1.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
// Data<Number,Number> detected2 = mDetected.get(1);
|
||||
// detected2.setXValue(mLTRNetDecoderInstrumented.getLTRDecoder().getErrorDetector().getDetectedZeroCrossing());
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* sdr-trunk
|
||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program.
|
||||
* If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.gui.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.LTRNetSampleBufferChart;
|
||||
@@ -142,8 +145,8 @@ public class LTRNetPane extends RealDecoderPane
|
||||
{
|
||||
if(mZeroCrossingErrorDetectorChart == null)
|
||||
{
|
||||
mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
|
||||
getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
|
||||
// mZeroCrossingErrorDetectorChart = new ZeroCrossingErrorDetectorChart(getDecoder(),
|
||||
// getDecoder().getLTRDecoder().getErrorDetector().getBuffer().length);
|
||||
}
|
||||
|
||||
return mZeroCrossingErrorDetectorChart;
|
||||
|
||||
@@ -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
|
||||
@@ -19,16 +19,12 @@
|
||||
|
||||
package io.github.dsheirer.gui.playlist.alias;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
import com.google.common.collect.Ordering;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.gui.playlist.Editor;
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import java.util.List;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
@@ -49,6 +45,7 @@ import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.util.Callback;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
/**
|
||||
* Editor for multiple selected aliases providing limited options for changing attributes of multiple aliases
|
||||
@@ -65,6 +62,8 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
private ToggleSwitch mMonitorAudioToggleSwitch;
|
||||
private ComboBox<Integer> mMonitorPriorityComboBox;
|
||||
private Button mApplyMonitorButton;
|
||||
private ToggleSwitch mRecordToggleSwitch;
|
||||
private Button mApplyRecordButton;
|
||||
|
||||
private BooleanProperty mChangeInProgressProperty;
|
||||
private ReadOnlyBooleanProperty mChangeInProgressROProperty;
|
||||
@@ -144,6 +143,17 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
GridPane.setConstraints(getApplyMonitorButton(), 4, row);
|
||||
gridPane.getChildren().add(getApplyMonitorButton());
|
||||
|
||||
Label recordLabel = new Label("Record");
|
||||
GridPane.setHalignment(recordLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(recordLabel, 0, ++row);
|
||||
gridPane.getChildren().add(recordLabel);
|
||||
|
||||
GridPane.setConstraints(getRecordToggleSwitch(), 1, row);
|
||||
gridPane.getChildren().add(getRecordToggleSwitch());
|
||||
|
||||
GridPane.setConstraints(getApplyRecordButton(), 4, row);
|
||||
gridPane.getChildren().add(getApplyRecordButton());
|
||||
|
||||
getChildren().add(gridPane);
|
||||
}
|
||||
|
||||
@@ -326,6 +336,19 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
return mMonitorAudioToggleSwitch;
|
||||
}
|
||||
|
||||
private ToggleSwitch getRecordToggleSwitch()
|
||||
{
|
||||
if(mRecordToggleSwitch == null)
|
||||
{
|
||||
mRecordToggleSwitch = new ToggleSwitch();
|
||||
mRecordToggleSwitch.setSelected(false);
|
||||
mRecordToggleSwitch.selectedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mRecordToggleSwitch;
|
||||
}
|
||||
|
||||
private ComboBox<Integer> getMonitorPriorityComboBox()
|
||||
{
|
||||
if(mMonitorPriorityComboBox == null)
|
||||
@@ -346,8 +369,6 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
return mMonitorPriorityComboBox;
|
||||
}
|
||||
|
||||
private static int setCount = 0;
|
||||
|
||||
private Button getApplyMonitorButton()
|
||||
{
|
||||
if(mApplyMonitorButton == null)
|
||||
@@ -378,7 +399,6 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
for(Alias alias : getItem())
|
||||
{
|
||||
alias.setCallPriority(pri);
|
||||
//System.out.println(++setCount);
|
||||
}
|
||||
|
||||
endChange();
|
||||
@@ -389,6 +409,25 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
return mApplyMonitorButton;
|
||||
}
|
||||
|
||||
private Button getApplyRecordButton()
|
||||
{
|
||||
if(mApplyRecordButton == null)
|
||||
{
|
||||
mApplyRecordButton = new Button("Apply");
|
||||
mApplyRecordButton.setOnAction(event -> {
|
||||
startChange();
|
||||
boolean record = getRecordToggleSwitch().isSelected();
|
||||
for(Alias alias : getItem())
|
||||
{
|
||||
alias.setRecordable(record);
|
||||
}
|
||||
endChange();
|
||||
});
|
||||
}
|
||||
|
||||
return mApplyRecordButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell factory for combo box for dislaying icon name and graphic
|
||||
*/
|
||||
|
||||
@@ -387,7 +387,7 @@ public class AliasConfigurationEditor extends SplitPane
|
||||
idsColumn.setCellValueFactory(new IdentifierCountCell());
|
||||
|
||||
TableColumn<Alias, Boolean> errorsColumn = new TableColumn<>("Error");
|
||||
errorsColumn.setPrefWidth(80);
|
||||
errorsColumn.setPrefWidth(120);
|
||||
errorsColumn.setCellValueFactory(new PropertyValueFactory<>("overlap"));
|
||||
errorsColumn.setCellFactory(param ->
|
||||
{
|
||||
|
||||
@@ -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,13 +34,16 @@ 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.P25FullyQualifiedRadio;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioFormatter;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormatter;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
@@ -63,6 +63,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 +111,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
|
||||
*/
|
||||
@@ -365,7 +364,10 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
for(AliasID aliasID: getIdentifiersList().getItems())
|
||||
{
|
||||
//Create a copy of the identifier so that the alias and the editor don't have the same instance
|
||||
alias.addAliasID(AliasFactory.copyOf(aliasID));
|
||||
//and reset the overlap flag (if set) so that the alias list can reevaluate the overlap state.
|
||||
AliasID copy = AliasFactory.copyOf(aliasID);
|
||||
copy.setOverlap(false);
|
||||
alias.addAliasID(copy);
|
||||
}
|
||||
|
||||
//Remove and replace alias actions
|
||||
@@ -684,8 +686,10 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
Menu p25Menu = new ProtocolMenu(Protocol.APCO25);
|
||||
p25Menu.getItems().add(new AddTalkgroupItem(Protocol.APCO25));
|
||||
p25Menu.getItems().add(new AddTalkgroupRangeItem(Protocol.APCO25));
|
||||
p25Menu.getItems().add(new AddP25FullyQualifiedTalkgroupItem());
|
||||
p25Menu.getItems().add(new AddRadioIdItem(Protocol.APCO25));
|
||||
p25Menu.getItems().add(new AddRadioIdRangeItem(Protocol.APCO25));
|
||||
p25Menu.getItems().add(new AddP25FullyQualifiedRadioIdItem());
|
||||
p25Menu.getItems().add(new SeparatorMenuItem());
|
||||
p25Menu.getItems().add(new AddUserStatusItem());
|
||||
p25Menu.getItems().add(new AddUnitStatusItem());
|
||||
@@ -717,6 +721,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 +1286,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
|
||||
*/
|
||||
@@ -1303,6 +1326,44 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu Item for adding a new protocol-specific Fully Qualified Radio ID alias identifier
|
||||
*/
|
||||
public class AddP25FullyQualifiedRadioIdItem extends MenuItem
|
||||
{
|
||||
public AddP25FullyQualifiedRadioIdItem()
|
||||
{
|
||||
super("Fully Qualified Radio ID");
|
||||
setOnAction(event -> {
|
||||
P25FullyQualifiedRadio radioId = new P25FullyQualifiedRadio();
|
||||
radioId.setProtocol(Protocol.APCO25);
|
||||
getIdentifiersList().getItems().add(radioId);
|
||||
getIdentifiersList().getSelectionModel().select(radioId);
|
||||
getIdentifiersList().scrollTo(radioId);
|
||||
modifiedProperty().set(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu Item for adding a new protocol-specific Fully Qualified Talkgroup alias identifier
|
||||
*/
|
||||
public class AddP25FullyQualifiedTalkgroupItem extends MenuItem
|
||||
{
|
||||
public AddP25FullyQualifiedTalkgroupItem()
|
||||
{
|
||||
super("Fully Qualified Talkgroup");
|
||||
setOnAction(event -> {
|
||||
P25FullyQualifiedTalkgroup talkgroup = new P25FullyQualifiedTalkgroup();
|
||||
talkgroup.setProtocol(Protocol.APCO25);
|
||||
getIdentifiersList().getItems().add(talkgroup);
|
||||
getIdentifiersList().getSelectionModel().select(talkgroup);
|
||||
getIdentifiersList().scrollTo(talkgroup);
|
||||
modifiedProperty().set(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu Item for adding a new protocol-specific Radio ID range alias identifier
|
||||
*/
|
||||
@@ -1486,7 +1547,25 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
|
||||
if(item != null)
|
||||
{
|
||||
if(item instanceof Talkgroup)
|
||||
if(item instanceof P25FullyQualifiedTalkgroup fqt)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("APCO-25 Fully Qualified Talkgroup:").append(fqt.getWacn());
|
||||
sb.append(".").append(fqt.getSystem());
|
||||
sb.append(".").append(fqt.getValue());
|
||||
|
||||
if(fqt.overlapProperty().get())
|
||||
{
|
||||
sb.append(" - Error: Overlap");
|
||||
}
|
||||
|
||||
if(!fqt.isValid())
|
||||
{
|
||||
sb.append(" **NOT VALID**");
|
||||
}
|
||||
setText(sb.toString());
|
||||
}
|
||||
else if(item instanceof Talkgroup)
|
||||
{
|
||||
Talkgroup talkgroup = (Talkgroup)item;
|
||||
Protocol protocol = talkgroup.getProtocol();
|
||||
@@ -1533,6 +1612,24 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
}
|
||||
setText(sb.toString());
|
||||
}
|
||||
else if(item instanceof P25FullyQualifiedRadio fqr)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("APCO-25 Fully Qualified Radio ID:").append(fqr.getWacn());
|
||||
sb.append(".").append(fqr.getSystem());
|
||||
sb.append(".").append(fqr.getValue());
|
||||
|
||||
if(fqr.overlapProperty().get())
|
||||
{
|
||||
sb.append(" - Error: Overlap");
|
||||
}
|
||||
|
||||
if(!fqr.isValid())
|
||||
{
|
||||
sb.append(" **NOT VALID**");
|
||||
}
|
||||
setText(sb.toString());
|
||||
}
|
||||
else if(item instanceof Radio)
|
||||
{
|
||||
Radio radio = (Radio)item;
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.playlist.alias.identifier;
|
||||
|
||||
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Editor for Digital Coded Squelch (DCS) alias identifiers
|
||||
*/
|
||||
public class DcsEditor extends IdentifierEditor<Dcs>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(DcsEditor.class);
|
||||
private ComboBox<DCSCode> mDCSCodeComboBox;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public DcsEditor()
|
||||
{
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setHgap(5);
|
||||
|
||||
Label typeLabel = new Label("DCS Code");
|
||||
GridPane.setConstraints(typeLabel, 0, 0);
|
||||
gridPane.getChildren().add(typeLabel);
|
||||
|
||||
GridPane.setConstraints(getDCSCodeComboBox(), 1, 0);
|
||||
gridPane.getChildren().add(getDCSCodeComboBox());
|
||||
|
||||
getChildren().add(gridPane);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(Dcs item)
|
||||
{
|
||||
super.setItem(item);
|
||||
if(item.isValid())
|
||||
{
|
||||
getDCSCodeComboBox().getSelectionModel().select(item.getDCSCode());
|
||||
}
|
||||
modifiedProperty().set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Combo-box loaded with DCS codes
|
||||
* @return
|
||||
*/
|
||||
private ComboBox<DCSCode> getDCSCodeComboBox()
|
||||
{
|
||||
if(mDCSCodeComboBox == null)
|
||||
{
|
||||
mDCSCodeComboBox = new ComboBox<>();
|
||||
mDCSCodeComboBox.getItems().addAll(DCSCode.STANDARD_CODES);
|
||||
mDCSCodeComboBox.getItems().addAll(DCSCode.INVERTED_CODES);
|
||||
mDCSCodeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
getItem().setDCSCode(getDCSCodeComboBox().getSelectionModel().getSelectedItem());
|
||||
modifiedProperty().set(true);
|
||||
});
|
||||
}
|
||||
|
||||
return mDCSCodeComboBox;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -35,10 +35,16 @@ public class IdentifierEditorFactory
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DCS:
|
||||
return new DcsEditor();
|
||||
case ESN:
|
||||
return new EsnEditor();
|
||||
case LOJACK:
|
||||
return new LojackEditor();
|
||||
case P25_FULLY_QUALIFIED_RADIO_ID:
|
||||
return new P25FullyQualifiedRadioIdEditor(userPreferences);
|
||||
case P25_FULLY_QUALIFIED_TALKGROUP:
|
||||
return new P25FullyQualifiedTalkgroupEditor(userPreferences);
|
||||
case RADIO_ID:
|
||||
return new RadioIdEditor(userPreferences);
|
||||
case RADIO_ID_RANGE:
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.radio.P25FullyQualifiedRadio;
|
||||
import io.github.dsheirer.gui.control.HexFormatter;
|
||||
import io.github.dsheirer.gui.control.IntegerFormatter;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Editor for P25 fully qualified radio alias identifiers
|
||||
*/
|
||||
public class P25FullyQualifiedRadioIdEditor extends IdentifierEditor<P25FullyQualifiedRadio>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(P25FullyQualifiedRadioIdEditor.class);
|
||||
|
||||
private UserPreferences mUserPreferences;
|
||||
private Label mProtocolLabel;
|
||||
private TextField mWacnField;
|
||||
private TextField mSystemField;
|
||||
private TextField mRadioField;
|
||||
private TextFormatter<Integer> mWacnTextFormatter;
|
||||
private TextFormatter<Integer> mSystemTextFormatter;
|
||||
private TextFormatter<Integer> mRadioTextFormatter;
|
||||
private WacnValueChangeListener mWacnValueChangeListener = new WacnValueChangeListener();
|
||||
private SystemValueChangeListener mSystemValueChangeListener = new SystemValueChangeListener();
|
||||
private RadioValueChangeListener mRadioValueChangeListener = new RadioValueChangeListener();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for determining user preferred talkgroup formats
|
||||
*/
|
||||
public P25FullyQualifiedRadioIdEditor(UserPreferences userPreferences)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setHgap(5);
|
||||
gridPane.setVgap(3);
|
||||
|
||||
GridPane.setConstraints(getProtocolLabel(), 0, 0);
|
||||
gridPane.getChildren().add(getProtocolLabel());
|
||||
|
||||
Label valueLabel = new Label("WACN");
|
||||
GridPane.setHalignment(valueLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(valueLabel, 1, 0);
|
||||
gridPane.getChildren().add(valueLabel);
|
||||
|
||||
GridPane.setConstraints(getWacnField(), 2, 0);
|
||||
gridPane.getChildren().add(getWacnField());
|
||||
|
||||
Label systemLabel = new Label("System");
|
||||
GridPane.setHalignment(systemLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(systemLabel, 3, 0);
|
||||
gridPane.getChildren().add(systemLabel);
|
||||
|
||||
GridPane.setConstraints(getSystemField(), 4, 0);
|
||||
gridPane.getChildren().add(getSystemField());
|
||||
|
||||
Label radioLabel = new Label("Radio ID");
|
||||
GridPane.setHalignment(radioLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(radioLabel, 5, 0);
|
||||
gridPane.getChildren().add(radioLabel);
|
||||
|
||||
GridPane.setConstraints(getRadioField(), 6, 0);
|
||||
gridPane.getChildren().add(getRadioField());
|
||||
|
||||
getChildren().add(gridPane);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(P25FullyQualifiedRadio item)
|
||||
{
|
||||
super.setItem(item);
|
||||
|
||||
P25FullyQualifiedRadio fqr = getItem();
|
||||
|
||||
getProtocolLabel().setDisable(fqr == null);
|
||||
getWacnField().setDisable(fqr == null);
|
||||
getSystemField().setDisable(fqr == null);
|
||||
getRadioField().setDisable(fqr == null);
|
||||
|
||||
if(fqr != null)
|
||||
{
|
||||
getProtocolLabel().setText(fqr.getProtocol().toString());
|
||||
updateTextFormatter();
|
||||
}
|
||||
else
|
||||
{
|
||||
getWacnField().setText(null);
|
||||
getSystemField().setText(null);
|
||||
getRadioField().setText(null);
|
||||
}
|
||||
|
||||
modifiedProperty().set(false);
|
||||
}
|
||||
|
||||
private void updateTextFormatter()
|
||||
{
|
||||
if(mWacnTextFormatter != null)
|
||||
{
|
||||
mWacnTextFormatter.valueProperty().removeListener(mWacnValueChangeListener);
|
||||
}
|
||||
if(mSystemTextFormatter != null)
|
||||
{
|
||||
mSystemTextFormatter.valueProperty().removeListener(mSystemValueChangeListener);
|
||||
}
|
||||
if(mRadioTextFormatter != null)
|
||||
{
|
||||
mRadioTextFormatter.valueProperty().removeListener(mRadioValueChangeListener);
|
||||
}
|
||||
|
||||
IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(getItem().getProtocol());
|
||||
|
||||
if(format == IntegerFormat.DECIMAL && (mRadioTextFormatter == null || !(mRadioTextFormatter instanceof IntegerFormatter)))
|
||||
{
|
||||
mWacnTextFormatter = new IntegerFormatter(0,0xFFFFF);
|
||||
mSystemTextFormatter = new IntegerFormatter(0,0xFFF);
|
||||
mRadioTextFormatter = new IntegerFormatter(0,0xFFFFFF);
|
||||
|
||||
mWacnField.setTooltip(new Tooltip("Format: 0 - 1048575"));
|
||||
mSystemField.setTooltip(new Tooltip("Format: 0 - 4095"));
|
||||
mRadioField.setTooltip(new Tooltip("Format: 0 - 16777215"));
|
||||
}
|
||||
else if(format == IntegerFormat.HEXADECIMAL && (mRadioTextFormatter == null || !(mRadioTextFormatter instanceof HexFormatter)))
|
||||
{
|
||||
mWacnTextFormatter = new HexFormatter(0,0xFFFFF);
|
||||
mSystemTextFormatter = new HexFormatter(0,0xFFF);
|
||||
mRadioTextFormatter = new HexFormatter(0,0xFFFFFF);
|
||||
|
||||
mWacnField.setTooltip(new Tooltip("Format: 0 - FFFFF"));
|
||||
mSystemField.setTooltip(new Tooltip("Format: 0 - FFF"));
|
||||
mRadioField.setTooltip(new Tooltip("Format: 0 - FFFFFF"));
|
||||
}
|
||||
|
||||
mWacnField.setTextFormatter(mWacnTextFormatter);
|
||||
mSystemField.setTextFormatter(mSystemTextFormatter);
|
||||
mRadioField.setTextFormatter(mRadioTextFormatter);
|
||||
|
||||
mWacnTextFormatter.setValue(getItem() != null ? getItem().getWacn() : null);
|
||||
mSystemTextFormatter.setValue(getItem() != null ? getItem().getSystem() : null);
|
||||
mRadioTextFormatter.setValue(getItem() != null ? getItem().getValue() : null);
|
||||
|
||||
mWacnTextFormatter.valueProperty().addListener(mWacnValueChangeListener);
|
||||
mSystemTextFormatter.valueProperty().addListener(mSystemValueChangeListener);
|
||||
mRadioTextFormatter.valueProperty().addListener(mRadioValueChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
private Label getProtocolLabel()
|
||||
{
|
||||
if(mProtocolLabel == null)
|
||||
{
|
||||
mProtocolLabel = new Label();
|
||||
}
|
||||
|
||||
return mProtocolLabel;
|
||||
}
|
||||
|
||||
private TextField getWacnField()
|
||||
{
|
||||
if(mWacnField == null)
|
||||
{
|
||||
mWacnField = new TextField();
|
||||
mWacnField.setTextFormatter(mWacnTextFormatter);
|
||||
}
|
||||
|
||||
return mWacnField;
|
||||
}
|
||||
|
||||
private TextField getSystemField()
|
||||
{
|
||||
if(mSystemField == null)
|
||||
{
|
||||
mSystemField = new TextField();
|
||||
mSystemField.setTextFormatter(mSystemTextFormatter);
|
||||
}
|
||||
|
||||
return mSystemField;
|
||||
}
|
||||
|
||||
private TextField getRadioField()
|
||||
{
|
||||
if(mRadioField == null)
|
||||
{
|
||||
mRadioField = new TextField();
|
||||
mRadioField.setTextFormatter(mRadioTextFormatter);
|
||||
}
|
||||
|
||||
return mRadioField;
|
||||
}
|
||||
|
||||
public class WacnValueChangeListener implements ChangeListener<Integer>
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
|
||||
{
|
||||
if(getItem() != null)
|
||||
{
|
||||
getItem().setWacn(newValue != null ? newValue : 0);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SystemValueChangeListener implements ChangeListener<Integer>
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
|
||||
{
|
||||
if(getItem() != null)
|
||||
{
|
||||
getItem().setSystem(newValue != null ? newValue : 0);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RadioValueChangeListener implements ChangeListener<Integer>
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
|
||||
{
|
||||
if(getItem() != null)
|
||||
{
|
||||
getItem().setValue(newValue != null ? newValue : 0);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.talkgroup.P25FullyQualifiedTalkgroup;
|
||||
import io.github.dsheirer.gui.control.HexFormatter;
|
||||
import io.github.dsheirer.gui.control.IntegerFormatter;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Editor for P25 fully qualified talkgroup alias identifiers
|
||||
*/
|
||||
public class P25FullyQualifiedTalkgroupEditor extends IdentifierEditor<P25FullyQualifiedTalkgroup>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(P25FullyQualifiedTalkgroupEditor.class);
|
||||
|
||||
private UserPreferences mUserPreferences;
|
||||
private Label mProtocolLabel;
|
||||
private TextField mWacnField;
|
||||
private TextField mSystemField;
|
||||
private TextField mTalkgroupField;
|
||||
private TextFormatter<Integer> mWacnTextFormatter;
|
||||
private TextFormatter<Integer> mSystemTextFormatter;
|
||||
private TextFormatter<Integer> mTalkgroupTextFormatter;
|
||||
private WacnValueChangeListener mWacnValueChangeListener = new WacnValueChangeListener();
|
||||
private SystemValueChangeListener mSystemValueChangeListener = new SystemValueChangeListener();
|
||||
private TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param userPreferences for determining user preferred talkgroup formats
|
||||
*/
|
||||
public P25FullyQualifiedTalkgroupEditor(UserPreferences userPreferences)
|
||||
{
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setHgap(5);
|
||||
gridPane.setVgap(3);
|
||||
|
||||
GridPane.setConstraints(getProtocolLabel(), 0, 0);
|
||||
gridPane.getChildren().add(getProtocolLabel());
|
||||
|
||||
Label valueLabel = new Label("WACN");
|
||||
GridPane.setHalignment(valueLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(valueLabel, 1, 0);
|
||||
gridPane.getChildren().add(valueLabel);
|
||||
|
||||
GridPane.setConstraints(getWacnField(), 2, 0);
|
||||
gridPane.getChildren().add(getWacnField());
|
||||
|
||||
Label systemLabel = new Label("System");
|
||||
GridPane.setHalignment(systemLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(systemLabel, 3, 0);
|
||||
gridPane.getChildren().add(systemLabel);
|
||||
|
||||
GridPane.setConstraints(getSystemField(), 4, 0);
|
||||
gridPane.getChildren().add(getSystemField());
|
||||
|
||||
Label radioLabel = new Label("Talkgroup");
|
||||
GridPane.setHalignment(radioLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(radioLabel, 5, 0);
|
||||
gridPane.getChildren().add(radioLabel);
|
||||
|
||||
GridPane.setConstraints(getTalkgroupField(), 6, 0);
|
||||
gridPane.getChildren().add(getTalkgroupField());
|
||||
|
||||
getChildren().add(gridPane);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItem(P25FullyQualifiedTalkgroup item)
|
||||
{
|
||||
super.setItem(item);
|
||||
|
||||
P25FullyQualifiedTalkgroup fqt = getItem();
|
||||
|
||||
getProtocolLabel().setDisable(fqt == null);
|
||||
getWacnField().setDisable(fqt == null);
|
||||
getSystemField().setDisable(fqt == null);
|
||||
getTalkgroupField().setDisable(fqt == null);
|
||||
|
||||
if(fqt != null)
|
||||
{
|
||||
getProtocolLabel().setText(fqt.getProtocol().toString());
|
||||
updateTextFormatter();
|
||||
}
|
||||
else
|
||||
{
|
||||
getWacnField().setText(null);
|
||||
getSystemField().setText(null);
|
||||
getTalkgroupField().setText(null);
|
||||
}
|
||||
|
||||
modifiedProperty().set(false);
|
||||
}
|
||||
|
||||
private void updateTextFormatter()
|
||||
{
|
||||
if(mWacnTextFormatter != null)
|
||||
{
|
||||
mWacnTextFormatter.valueProperty().removeListener(mWacnValueChangeListener);
|
||||
}
|
||||
if(mSystemTextFormatter != null)
|
||||
{
|
||||
mSystemTextFormatter.valueProperty().removeListener(mSystemValueChangeListener);
|
||||
}
|
||||
if(mTalkgroupTextFormatter != null)
|
||||
{
|
||||
mTalkgroupTextFormatter.valueProperty().removeListener(mTalkgroupValueChangeListener);
|
||||
}
|
||||
|
||||
IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(getItem().getProtocol());
|
||||
|
||||
if(format == IntegerFormat.DECIMAL && (mTalkgroupTextFormatter == null || !(mTalkgroupTextFormatter instanceof IntegerFormatter)))
|
||||
{
|
||||
mWacnTextFormatter = new IntegerFormatter(0,0xFFFFF);
|
||||
mSystemTextFormatter = new IntegerFormatter(0,0xFFF);
|
||||
mTalkgroupTextFormatter = new IntegerFormatter(0,0xFFFF);
|
||||
|
||||
mWacnField.setTooltip(new Tooltip("Format: 0 - 1048575"));
|
||||
mSystemField.setTooltip(new Tooltip("Format: 0 - 4095"));
|
||||
mTalkgroupField.setTooltip(new Tooltip("Format: 0 - 65535"));
|
||||
}
|
||||
else if(format == IntegerFormat.HEXADECIMAL && (mTalkgroupTextFormatter == null || !(mTalkgroupTextFormatter instanceof HexFormatter)))
|
||||
{
|
||||
mWacnTextFormatter = new HexFormatter(0,0xFFFFF);
|
||||
mSystemTextFormatter = new HexFormatter(0,0xFFF);
|
||||
mTalkgroupTextFormatter = new HexFormatter(0,0xFFFFFF);
|
||||
|
||||
mWacnField.setTooltip(new Tooltip("Format: 0 - FFFFF"));
|
||||
mSystemField.setTooltip(new Tooltip("Format: 0 - FFF"));
|
||||
mTalkgroupField.setTooltip(new Tooltip("Format: 0 - FFFF"));
|
||||
}
|
||||
|
||||
mWacnField.setTextFormatter(mWacnTextFormatter);
|
||||
mSystemField.setTextFormatter(mSystemTextFormatter);
|
||||
mTalkgroupField.setTextFormatter(mTalkgroupTextFormatter);
|
||||
|
||||
mWacnTextFormatter.setValue(getItem() != null ? getItem().getWacn() : null);
|
||||
mSystemTextFormatter.setValue(getItem() != null ? getItem().getSystem() : null);
|
||||
mTalkgroupTextFormatter.setValue(getItem() != null ? getItem().getValue() : null);
|
||||
|
||||
mWacnTextFormatter.valueProperty().addListener(mWacnValueChangeListener);
|
||||
mSystemTextFormatter.valueProperty().addListener(mSystemValueChangeListener);
|
||||
mTalkgroupTextFormatter.valueProperty().addListener(mTalkgroupValueChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
//no-op
|
||||
}
|
||||
|
||||
private Label getProtocolLabel()
|
||||
{
|
||||
if(mProtocolLabel == null)
|
||||
{
|
||||
mProtocolLabel = new Label();
|
||||
}
|
||||
|
||||
return mProtocolLabel;
|
||||
}
|
||||
|
||||
private TextField getWacnField()
|
||||
{
|
||||
if(mWacnField == null)
|
||||
{
|
||||
mWacnField = new TextField();
|
||||
mWacnField.setTextFormatter(mWacnTextFormatter);
|
||||
}
|
||||
|
||||
return mWacnField;
|
||||
}
|
||||
|
||||
private TextField getSystemField()
|
||||
{
|
||||
if(mSystemField == null)
|
||||
{
|
||||
mSystemField = new TextField();
|
||||
mSystemField.setTextFormatter(mSystemTextFormatter);
|
||||
}
|
||||
|
||||
return mSystemField;
|
||||
}
|
||||
|
||||
private TextField getTalkgroupField()
|
||||
{
|
||||
if(mTalkgroupField == null)
|
||||
{
|
||||
mTalkgroupField = new TextField();
|
||||
mTalkgroupField.setTextFormatter(mTalkgroupTextFormatter);
|
||||
}
|
||||
|
||||
return mTalkgroupField;
|
||||
}
|
||||
|
||||
public class WacnValueChangeListener implements ChangeListener<Integer>
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
|
||||
{
|
||||
if(getItem() != null)
|
||||
{
|
||||
getItem().setWacn(newValue != null ? newValue : 0);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SystemValueChangeListener implements ChangeListener<Integer>
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
|
||||
{
|
||||
if(getItem() != null)
|
||||
{
|
||||
getItem().setSystem(newValue != null ? newValue : 0);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TalkgroupValueChangeListener implements ChangeListener<Integer>
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
|
||||
{
|
||||
if(getItem() != null)
|
||||
{
|
||||
getItem().setValue(newValue != null ? newValue : 0);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,9 +92,9 @@ public class AMConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public AMConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -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
|
||||
@@ -38,6 +38,8 @@ import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -69,9 +71,6 @@ import org.controlsfx.control.ToggleSwitch;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Channel configuration editor
|
||||
*/
|
||||
@@ -100,6 +99,7 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
private IconNode mPlayGraphicNode;
|
||||
private IconNode mStopGraphicNode;
|
||||
private ChannelProcessingMonitor mChannelProcessingMonitor = new ChannelProcessingMonitor();
|
||||
private IFilterProcessor mFilterProcessor;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
@@ -108,11 +108,12 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public ChannelConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
mPlaylistManager = playlistManager;
|
||||
mTunerManager = tunerManager;
|
||||
mUserPreferences = userPreferences;
|
||||
mFilterProcessor = filterProcessor;
|
||||
|
||||
setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
@@ -689,7 +690,16 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
mSaveButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mSaveButton.disableProperty().bind(modifiedProperty().not());
|
||||
mSaveButton.setOnAction(event -> {
|
||||
save();
|
||||
if(mFilterProcessor != null)
|
||||
{
|
||||
mFilterProcessor.clearFilter();
|
||||
save();
|
||||
mFilterProcessor.restoreFilter();
|
||||
}
|
||||
else
|
||||
{
|
||||
save();
|
||||
}
|
||||
|
||||
if(getItem().isProcessing())
|
||||
{
|
||||
|
||||
@@ -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,10 @@ import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides access to channel configuration editors for various decoder types.
|
||||
@@ -41,31 +40,35 @@ public class ChannelConfigurationEditorFactory
|
||||
* Constructs an editor for the specified decoder type
|
||||
* @param decoderType to create
|
||||
* @param playlistManager for the editor
|
||||
* @param tunerManager for tuners
|
||||
* @param userPreferences for preferences
|
||||
* @param filterProcessor to be notified to clear and restore any applied filters.
|
||||
* @return constructed editor
|
||||
*/
|
||||
public static ChannelConfigurationEditor getEditor(DecoderType decoderType, PlaylistManager playlistManager,
|
||||
TunerManager tunerManager, UserPreferences userPreferences)
|
||||
TunerManager tunerManager, UserPreferences userPreferences,
|
||||
IFilterProcessor filterProcessor)
|
||||
{
|
||||
switch(decoderType)
|
||||
{
|
||||
case AM:
|
||||
return new AMConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new AMConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case DMR:
|
||||
return new DMRConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new DMRConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case NBFM:
|
||||
return new NBFMConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new NBFMConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case LTR_NET:
|
||||
return new LTRNetConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new LTRNetConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case LTR:
|
||||
return new LTRConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new LTRConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case MPT1327:
|
||||
return new MPT1327ConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new MPT1327ConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case PASSPORT:
|
||||
return new PassportConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new PassportConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case P25_PHASE1:
|
||||
return new P25P1ConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new P25P1ConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
case P25_PHASE2:
|
||||
return new P25P2ConfigurationEditor(playlistManager, tunerManager, userPreferences);
|
||||
return new P25P2ConfigurationEditor(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
default:
|
||||
if(decoderType != null && !mLoggedUnrecognizedTypes.contains(decoderType))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -26,6 +26,12 @@ import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -63,17 +69,10 @@ import org.controlsfx.control.textfield.TextFields;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* JavaFX editor for managing channel configurations.
|
||||
*/
|
||||
public class ChannelEditor extends SplitPane
|
||||
public class ChannelEditor extends SplitPane implements IFilterProcessor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelEditor.class);
|
||||
private PlaylistManager mPlaylistManager;
|
||||
@@ -106,7 +105,8 @@ public class ChannelEditor extends SplitPane
|
||||
mPlaylistManager = playlistManager;
|
||||
mTunerManager = tunerManager;
|
||||
mUserPreferences = userPreferences;
|
||||
mUnknownConfigurationEditor = new UnknownConfigurationEditor(mPlaylistManager, mTunerManager, userPreferences);
|
||||
mUnknownConfigurationEditor = new UnknownConfigurationEditor(mPlaylistManager, mTunerManager,
|
||||
userPreferences, this);
|
||||
|
||||
HBox channelsBox = new HBox();
|
||||
channelsBox.setSpacing(10.0);
|
||||
@@ -203,7 +203,7 @@ public class ChannelEditor extends SplitPane
|
||||
if(editor == null)
|
||||
{
|
||||
editor = ChannelConfigurationEditorFactory.getEditor(channelDecoderType, mPlaylistManager,
|
||||
mTunerManager, mUserPreferences);
|
||||
mTunerManager, mUserPreferences, this);
|
||||
|
||||
if(editor != null)
|
||||
{
|
||||
@@ -374,6 +374,24 @@ public class ChannelEditor extends SplitPane
|
||||
mChannelFilteredList.setPredicate(mChannelListFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily clear the filter when the channel configuration editor is applying changes.
|
||||
*/
|
||||
@Override
|
||||
public void clearFilter()
|
||||
{
|
||||
mChannelFilteredList.setPredicate(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the filter once the channel configuration editor has completed applying changes.
|
||||
*/
|
||||
@Override
|
||||
public void restoreFilter()
|
||||
{
|
||||
mChannelFilteredList.setPredicate(mChannelListFilter);
|
||||
}
|
||||
|
||||
private TableView<Channel> getChannelTableView()
|
||||
{
|
||||
if(mChannelTableView == null)
|
||||
@@ -449,6 +467,25 @@ public class ChannelEditor extends SplitPane
|
||||
nameColumn.setPrefWidth(200);
|
||||
|
||||
TableColumn frequencyColumn = new TableColumn("Frequency");
|
||||
frequencyColumn.setComparator((o1, o2) -> {
|
||||
if(o1 instanceof Channel c1 && o2 instanceof Channel c2)
|
||||
{
|
||||
if(!c1.getFrequencyList().isEmpty() && !c2.getFrequencyList().isEmpty())
|
||||
{
|
||||
return Long.compare(c1.getFrequencyList().get(0), c2.getFrequencyList().get(0));
|
||||
}
|
||||
else if(!c1.getFrequencyList().isEmpty())
|
||||
{
|
||||
return Long.compare(c1.getFrequencyList().get(0), 0l);
|
||||
}
|
||||
else if(!c2.getFrequencyList().isEmpty())
|
||||
{
|
||||
return Long.compare(0l, c2.getFrequencyList().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
return Integer.compare(o1.hashCode(), o2.hashCode());
|
||||
});
|
||||
frequencyColumn.setCellValueFactory(new FrequencyCellValueFactory());
|
||||
frequencyColumn.setPrefWidth(100);
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -99,9 +99,9 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public DMRConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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.channel;
|
||||
|
||||
/**
|
||||
* Interface for a processor that implements a filtering mechanism and can be commanded by an external controller to
|
||||
* temporarily clear a filter and then restore the filter.
|
||||
*/
|
||||
public interface IFilterProcessor
|
||||
{
|
||||
void clearFilter();
|
||||
void restoreFilter();
|
||||
}
|
||||
@@ -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
|
||||
@@ -37,6 +37,8 @@ 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.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -50,9 +52,6 @@ import org.controlsfx.control.SegmentedButton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* LTR (standard) channel configuration editor
|
||||
*/
|
||||
@@ -77,9 +76,9 @@ public class LTRConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public LTRConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
|
||||
|
||||
@@ -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
|
||||
@@ -37,6 +37,8 @@ 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.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -50,9 +52,6 @@ import org.controlsfx.control.SegmentedButton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* LTR-Net channel configuration editor
|
||||
*/
|
||||
@@ -76,9 +75,10 @@ public class LTRNetConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param tunerManager for tuners
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public LTRNetConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager, UserPreferences userPreferences)
|
||||
public LTRNetConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
|
||||
|
||||
@@ -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
|
||||
@@ -38,6 +38,8 @@ 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.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.HPos;
|
||||
@@ -56,9 +58,6 @@ import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static io.github.dsheirer.module.decode.config.DecodeConfiguration.CALL_TIMEOUT_MAXIMUM;
|
||||
import static io.github.dsheirer.module.decode.config.DecodeConfiguration.CALL_TIMEOUT_MINIMUM;
|
||||
|
||||
@@ -87,9 +86,9 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public MPT1327ConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -58,15 +58,12 @@ 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;
|
||||
|
||||
/**
|
||||
* 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 +72,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 +82,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
|
||||
@@ -95,9 +93,9 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public NBFMConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
|
||||
@@ -162,12 +160,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 +276,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 +315,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 +462,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 +479,8 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
getSquelchThresholdField().setDisable(true);
|
||||
getSquelchAutoTrackSwitch().setDisable(true);
|
||||
getSquelchAutoTrackSwitch().setSelected(false);
|
||||
getAudioFilterEnable().setDisable(true);
|
||||
getAudioFilterEnable().setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +517,7 @@ public class NBFMConfigurationEditor extends ChannelConfigurationEditor
|
||||
config.setTalkgroup(talkgroup);
|
||||
config.setSquelchThreshold(mSquelchTextFormatter.getValue());
|
||||
config.setSquelchAutoTrack(getSquelchAutoTrackSwitch().isSelected());
|
||||
config.setAudioFilter(getAudioFilterEnable().isSelected());
|
||||
getItem().setDecodeConfiguration(config);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-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
|
||||
@@ -36,6 +36,8 @@ 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.geometry.HPos;
|
||||
@@ -54,9 +56,6 @@ import org.controlsfx.control.ToggleSwitch;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* P25 Phase 1 channel configuration editor
|
||||
*/
|
||||
@@ -83,9 +82,9 @@ public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences for preferences
|
||||
*/
|
||||
public P25P1ConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -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
|
||||
@@ -37,6 +37,8 @@ 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.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -46,9 +48,6 @@ import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* P25 Phase 2 channel configuration editor
|
||||
*/
|
||||
@@ -72,9 +71,9 @@ public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences
|
||||
*/
|
||||
public P25P2ConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
|
||||
@@ -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
|
||||
@@ -36,6 +36,8 @@ 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.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TitledPane;
|
||||
@@ -43,9 +45,6 @@ import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Passport channel configuration editor
|
||||
*/
|
||||
@@ -68,9 +67,9 @@ public class PassportConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param userPreferences
|
||||
*/
|
||||
public PassportConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
|
||||
|
||||
@@ -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
|
||||
@@ -45,9 +45,9 @@ public class UnknownConfigurationEditor extends ChannelConfigurationEditor
|
||||
* @param aliasModel
|
||||
*/
|
||||
public UnknownConfigurationEditor(PlaylistManager playlistManager, TunerManager tunerManager,
|
||||
UserPreferences userPreferences)
|
||||
UserPreferences userPreferences, IFilterProcessor filterProcessor)
|
||||
{
|
||||
super(playlistManager, tunerManager, userPreferences);
|
||||
super(playlistManager, tunerManager, userPreferences, filterProcessor);
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.rrapi.type.Agency;
|
||||
import io.github.dsheirer.rrapi.type.AgencyInfo;
|
||||
import io.github.dsheirer.rrapi.type.CountyInfo;
|
||||
import io.github.dsheirer.service.radioreference.RadioReference;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import javafx.application.Platform;
|
||||
@@ -158,8 +159,13 @@ public class AgencyEditor extends VBox
|
||||
ThreadPool.CACHED.submit(() -> {
|
||||
try
|
||||
{
|
||||
final AgencyInfo agencyInfo = mRadioReference.getService().getAgencyInfo(agency);
|
||||
Platform.runLater(() -> getAgencyFrequencyEditor().setCategories(agencyInfo.getCategories()));
|
||||
if (mLevel == Level.COUNTY && agency instanceof CountyAgency) {
|
||||
final CountyInfo countyInfo = mRadioReference.getService().getCountyInfo(-agency.getAgencyId());
|
||||
Platform.runLater(() -> getAgencyFrequencyEditor().setCategories(countyInfo.getCategories()));
|
||||
} else {
|
||||
final AgencyInfo agencyInfo = mRadioReference.getService().getAgencyInfo(agency);
|
||||
Platform.runLater(() -> getAgencyFrequencyEditor().setCategories(agencyInfo.getCategories()));
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
@@ -196,11 +202,11 @@ public class AgencyEditor extends VBox
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if(o1.getName() == null)
|
||||
else if(o1.getName() == null || o2 instanceof CountyAgency)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if(o2.getName() == null)
|
||||
else if(o2.getName() == null || o1 instanceof CountyAgency)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -158,9 +158,12 @@ public class AgencyFrequencyEditor extends GridPane
|
||||
|
||||
if(categories != null && !categories.isEmpty())
|
||||
{
|
||||
getCategoryComboBox().getItems().add(ALL_CATEGORIES);
|
||||
// Only allow the combined list of all categories if there aren't that many
|
||||
if (categories.size() < 10) {
|
||||
categories.add(0, ALL_CATEGORIES);
|
||||
}
|
||||
getCategoryComboBox().getItems().addAll(categories);
|
||||
getCategoryComboBox().getSelectionModel().select(ALL_CATEGORIES);
|
||||
getCategoryComboBox().getSelectionModel().select(categories.get(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This 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.radioreference;
|
||||
|
||||
import io.github.dsheirer.rrapi.type.Agency;
|
||||
import io.github.dsheirer.rrapi.type.CountyInfo;
|
||||
|
||||
public class CountyAgency extends Agency
|
||||
{
|
||||
public CountyAgency(CountyInfo countyInfo)
|
||||
{
|
||||
this.setAgencyId(-countyInfo.getCountyId());
|
||||
this.setName(countyInfo.getName() + " County (All)");
|
||||
this.setType(-1);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* 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;
|
||||
|
||||
@@ -22,6 +22,7 @@ package io.github.dsheirer.gui.playlist.radioreference;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.rrapi.RadioReferenceException;
|
||||
import io.github.dsheirer.rrapi.type.Agency;
|
||||
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
|
||||
import io.github.dsheirer.rrapi.type.Country;
|
||||
import io.github.dsheirer.rrapi.type.CountryInfo;
|
||||
@@ -55,6 +56,7 @@ import jiconfont.javafx.IconNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -689,7 +691,12 @@ public class RadioReferenceEditor extends BorderPane implements Consumer<Authori
|
||||
|
||||
Platform.runLater(() -> {
|
||||
getCountySystemEditor().setSystems(countyInfo.getSystems());
|
||||
getCountyAgencyEditor().setAgencies(countyInfo.getAgencies());
|
||||
|
||||
// Make a new list so we don't add our CountyAgency to the original list of agencies
|
||||
List<Agency> countyAgencies = new ArrayList<Agency>();
|
||||
countyAgencies.add(new CountyAgency(countyInfo));
|
||||
countyAgencies.addAll(countyInfo.getAgencies());
|
||||
getCountyAgencyEditor().setAgencies(countyAgencies);
|
||||
});
|
||||
}
|
||||
catch(RadioReferenceException rre)
|
||||
|
||||
@@ -294,7 +294,11 @@ public class SystemEditor extends VBox
|
||||
{
|
||||
CountyInfo countyInfo = null;
|
||||
|
||||
if(site.getCountyId() > 0)
|
||||
int countyId = site.getCountyId();
|
||||
|
||||
//Temporary sites that have been added to a system where the county/location is unknown, can
|
||||
//have a county ID of 99,999 as observed from API (undocumented).
|
||||
if(countyId > 0 && countyId != 99999)
|
||||
{
|
||||
countyInfo = mRadioReference.getService().getCountyInfo(site.getCountyId());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -40,6 +40,8 @@ public class StreamEditorFactory
|
||||
return new BroadcastifyStreamEditor(playlistManager);
|
||||
case RDIOSCANNER_CALL:
|
||||
return new RdioScannerEditor(playlistManager);
|
||||
case OPENMHZ:
|
||||
return new OpenMHzEditor(playlistManager);
|
||||
case BROADCASTIFY_CALL:
|
||||
return new BroadcastifyCallEditor(playlistManager);
|
||||
case ICECAST_HTTP:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user