Compare commits

...

122 Commits

Author SHA1 Message Date
Denny Sheirer
88580af436 Merge pull request #284 from DSheirer/283-broadcastify-text-html-response
283 Broadcastify mount point in use error
2017-11-04 13:31:40 +00:00
Denny
64688a3c20 Updates CHANGELOG 2017-11-04 09:07:46 -04:00
Denny
8be3399a6b Issue #283
Updates Icecast TCP broadcaster to correctly handle content type:text/html
message that normally follows mount point in use error received from
broadcastify.

Changes mount point in use broadcast state to be a temporary error so that
the stream will attempt to reconnect after receiving this error.

Updates logging to include memory allocation/usage metrics with each
logging entry in order to support debugging issues related to high
memory usage situations.  Updates initial application logging to better
identify the host system operating environment.
2017-11-04 09:02:03 -04:00
Denny Sheirer
28340809ca Merge pull request #279 from DSheirer/276-traffic-channel-manager-deadlocked-threads
276 TrafficController Infinite Loop
2017-10-21 09:43:42 -04:00
Denny
f3d3a8187a Resolves #276 Traffic Channel threads locking in overflow state. Issue
occured when the ChannelProcessionManager was allocating a new channel
causing the tuner to invoke the updateLOFrequency() method that was
trying to find a center tune frequency that would accommodate all of the
channels, specifically in the instance where the distance between the
highest channel and the middle channel was exactly one-half of the
tuner's bandwidth.  This situation would cause the calculated adjustment
to be zero and would cause an infinite loop, locking all channels from
accessing the any channel startup/teardown events and causing running
channels to block with buffer overflows. Added 1 to the adjustment to
ensure that the initial adjustment placed the center overlapping channel
fully to the left of the center DC spike so that on the next iteration
that channel would no longer be overlapping.
2017-10-21 09:31:34 -04:00
Denny Sheirer
8484e837b8 Merge pull request #277 from DSheirer/273-gui-title-version
273 Application Title Version String
2017-10-16 19:38:15 -04:00
Denny
01b188ea25 Resolves #273 issue with dangling dash in application title version string. 2017-10-16 19:37:11 -04:00
Denny Sheirer
108691dc3c Merge pull request #274 from DSheirer/272-java-9-compatibility
272 java 9 compatibility
2017-10-09 12:40:04 -04:00
Denny
455aa97286 Updates build number to 'final' 2017-10-09 12:35:28 -04:00
Denny
d608834214 Resolves #272. Updates the windows launch script to perform conditional
launching based on the JRE version 8 or 9.
2017-10-09 16:33:58 +00:00
Denny
41660e1881 Updates the build number to 0.3.1 for the next patch release
Updates the build script for conditional compilation using either Java
8 or Java 9 options.  Java 9 deprecated the java.xml.bind package and
removed it as a default Java SE package, so we have to reference it via
the --add-modules argument.

Updates the linux/mac launch script to check the java runtime version
and use either a Java 8 or Java 9 launch command.
2017-10-08 09:00:10 -04:00
Denny Sheirer
110ccb158c Update README.md
Removed Version 0.2.0 user manual link
2017-09-22 06:16:34 -04:00
Denny
64ccacf04a Releasing Version 0.3.0 (final). 2017-09-22 05:56:27 -04:00
Denny Sheirer
f00b9555fe Merge pull request #271 from DSheirer/269-viterbi-3-4-rate
269 Viterbi decoder for DMR 3/4-rate packet messages
2017-09-15 11:50:19 -04:00
Denny
85442a9648 Resolves #269 - creates a generic Viterbi decoder and a Digital Mobile Radio (DMR) 3/4 Rate Viterbi decoder implementation for decoding Trellis Coded Modulation (TCM) encoded DMR Data packets. 2017-09-15 11:40:23 -04:00
Denny Sheirer
c67a0b329d Merge pull request #267 from DSheirer/266-licence-directory
266 Update build script to use gitlab license file
2017-09-10 04:55:57 -04:00
Denny
8035d64fcd Resolves #266 - updates build script to use new gitlab license file and
removes previous license file.
2017-09-10 04:54:26 -04:00
Denny Sheirer
df2a061a08 Create LICENSE 2017-08-28 19:51:13 -04:00
Denny
e430319937 Updates build to 0.3.0 Beta 14 2017-08-19 07:15:57 -04:00
Denny Sheirer
724f769771 Merge pull request #262 from DSheirer/260-icecast-stream-doesn't-reconnect
260 - Icecast and Shoutcast streaming doesn't auto-reconnect
2017-08-19 07:14:55 -04:00
Denny
c196cfebed Resolves issue #260 - applies changes to Shoutcast V1 and v2 that were
applied to the Icecast streamer for issue releated to reconnecting.
2017-08-19 07:13:08 -04:00
Denny
e80b5866d4 Resolves issue #260 where audio streaming was not re-connecting after a
disconnect.
2017-08-18 20:07:53 -04:00
Denny
c8994d5593 Updates build to version 0.3.0 Beta 13 2017-07-22 05:45:36 -04:00
Denny Sheirer
99ef57c90f Merge pull request #255 from DSheirer/249-overflow-teardown-lockup
249 Channel TEARDOWN/OVERFLOW state lockup
2017-07-22 05:44:12 -04:00
Denny
be62ae0459 Resolves #249 - removes the state monitor runnable from the channel
state and replaces it with a processing chain heartbeat that is generated
by the sample source.

Previously, the state monitor was running on a separate thread and this
was causing multi-threading issues.  With this update, all channel state
operations (and all module operations) occur on the source's buffer
processing thread.
2017-07-22 05:42:13 -04:00
Denny Sheirer
b9be6f43e0 Merge pull request #253 from DSheirer/252-usb-transfer-processor-bad-logging
252 USB transfer processor erroneous logging and performance enhancements
2017-07-09 12:29:21 +00:00
Denny
d5ad3f807d Resolves issue where the tuner's buffer processor is logging that an overflow state has been cleared, caused by a clearing of the buffer when the tuner is being shutdown because there are no more channel consumers registered on the tuner.
Tweaked buffer size limits and buffer handling to attempt to avoid overflow situations in the first place.

Resolved an issue where the tuner processor was not deregistering correctly from the LIBUSB tuner processor during tuner shutdown.
2017-07-09 08:21:07 -04:00
Denny Sheirer
d85ebd018d Merge pull request #251 from DSheirer/250-state-monitor-throwable
StateMonitor - try/catch for Throwables
2017-07-01 11:39:21 +00:00
Denny
ab4e31ae00 Ensures that any leftover cached LDU1 message is reset when the call ends. 2017-07-01 07:36:55 -04:00
Denny
5efd89632e Ensures that any leftover cached LDU1 message is reset when the call ends. 2017-05-28 07:30:05 -04:00
Denny
1dc3863489 Updates build to 0.3.0 Beta 12 2017-05-28 07:22:43 -04:00
Denny Sheirer
441286ba13 Merge pull request #247 from DSheirer/244-ldu1-encryption-flag
244 Prevent P25 Encrypted Audio Decoding/Playback
2017-05-28 11:20:20 +00:00
Denny
e4323df347 Resolves issue #244 - updates the P25 IMBE audio module to correctly
detect encrypted audio state so that encrypted audio is not (incorrectly)
decoded and played back to the user.

Processes HDU and LDU2 messages to identify encrypted call state.  Once
established, all subsequent LDU1/2 messages are decoded only if the call
is unencrypted.  If an LDU1 message is received out-of-sequence, meaning
that the initial HDU message was missed, then the LDU1 is cached until
we receive an LDU2 message and correctly determine the call encryption
state.
2017-05-28 07:17:09 -04:00
Denny
a872e0d664 Updates version to Beta 0.3.0 Beta 11 2017-05-20 07:24:27 -04:00
Denny Sheirer
fbc92cfe31 Merge pull request #246 from DSheirer/245-sound-card-source-issue
Using sound card or mixer as a source throws an error
2017-05-20 10:55:23 +00:00
Denny
1a432197ac Resolves #245 issue with NPE for frequency change event listener when using the sound card/mixer as a source. 2017-05-20 06:51:53 -04:00
Denny
fbc6e508bd Increments build to 0.3.0-beta10 2017-04-14 07:46:18 -04:00
Denny Sheirer
f9b6e67b00 Merge pull request #240 from DSheirer/234-long-alias-names-cause-audio-label-to-wrap
Long alias names cause audio label to wrap
2017-03-31 01:13:17 +00:00
Denny
a628800a71 Resolves #234 - updates the miglayout layout manager to allow jlabel text strings to be automatically truncated (with ellipses) when the alias name is too long. 2017-03-30 21:12:06 -04:00
Denny
25f71a76c4 Resolves #234 - hard codes alias name truncation when name exceeds 33 characters in length. 2017-03-30 20:03:48 -04:00
Denny
5fe8ccd478 Resolves #234 - change the order of assigning an icon and alias text to the audio panel in an attempt to prevent the audio channel panel from wrapping to the next line when the text is too long to display. 2017-03-30 19:52:35 -04:00
Denny Sheirer
069f951a86 Merge pull request #239 from DSheirer/228-traffic-channel-allocation-for-do-not-monitor
Traffic channel manager now allocates traffic channels for do not monitor aliases
2017-03-30 23:27:38 +00:00
Denny
dab6df7836 Resolves #228 - traffic channel manager now allocates traffic channels even when the alias is marked as do not monitor 2017-03-30 19:26:35 -04:00
Denny Sheirer
89da46b63c Merge pull request #238 from DSheirer/235-lojack-decoder-error-on-construction
Resolves error on constructing a LoJack decoder state.
2017-03-30 23:19:29 +00:00
Denny
5f2ce2a9f2 Resolves #235 - fixes error on constructing a LoJack decoder state. 2017-03-30 19:18:39 -04:00
Denny Sheirer
7a0b377971 Merge pull request #232 from DSheirer/229-p25-phase2-event-duplicates
Resolves #229 - duplicate P25 TDMA call events.
2017-03-23 21:52:47 +00:00
Denny
5dbe0ecce3 Resolves #229 - duplicate P25 TDMA call events. 2017-03-23 21:48:52 +00:00
Denny Sheirer
6647c581a6 Merge pull request #231 from DSheirer/230-p25-tdma-frequency-bands
P25 Phase II TDMA frequency bands are now displayed in details summary
2017-03-23 21:01:58 +00:00
Denny
a2ab5bfa95 Resolves #230 - P25 Phase II TDMA frequency bands are now displayed in the channel details summary. 2017-03-23 20:55:42 +00:00
Denny
ed486440b8 Merge remote-tracking branch 'origin/master' 2017-03-12 11:33:17 -04:00
Denny
13464e01fc Updates build to 0.3.0 Beta 9 2017-03-12 11:33:04 -04:00
Denny Sheirer
ea46a8b0e8 Merge pull request #225 from DSheirer/222-spectral-display-of-frequency-correction-offset
Frequency change event handling in the processing chain
2017-03-11 17:03:34 +00:00
Denny
d510c34356 Adds frequency change event listener handler to LSM decoder. 2017-03-11 12:01:45 -05:00
Denny
ae8a44caed Removes cic cleanup filter from processing chain. 2017-03-11 11:53:12 -05:00
Denny
1aa85a86ca Resolves issue with channel frequency correction events not being broadcast within the processing chain and rendering in the spectral display. 2017-03-11 10:16:08 -05:00
Denny Sheirer
17539d4d55 Merge pull request #224 from DSheirer/223-now-playing-frequency-format
Now Playing frequency display formatting
2017-03-11 11:14:58 +00:00
Denny
1db1d28390 Resolves issue with frequency display formating in the Now Playing window. 2017-03-11 06:13:59 -05:00
Denny Sheirer
37a941e904 Merge pull request #219 from DSheirer/218-audio-priority-
Resolves issue with audio playback allowing higher priority audio to preempt lower priority audio
2017-03-05 12:34:09 +00:00
Denny
57f6655824 Resolves issue where higher priority audio was not preempting lower priority audio. 2017-03-05 07:32:42 -05:00
Denny Sheirer
4cd8f8209f Merge pull request #216 from DSheirer/215-audio-recording-handles-part-2
215 audio recording handles part 2
2017-03-03 15:49:24 +00:00
Denny
61bfef5798 Incrementing build to version 0.3.0 beta8 2017-03-03 10:44:50 -05:00
Denny
ef172a04c6 Cleanup - removed synchronized blocks since the buffer processor thread is the only thread managing recorders. 2017-03-03 10:04:21 -05:00
Denny
a877afcceb Updates buffer processing in recorder manager so that we're not blocking any of the decoding channel producer threads that are trying to push audio packets to the recorder manager. Adds an overflowable packet queue and services the queue once a second followed by an idle recorder check (and disposal). 2017-03-03 10:01:32 -05:00
Denny
980ad72d4d Incrementing build to 0.3.0 beta7 2017-03-03 06:01:09 -05:00
Denny Sheirer
9b746cc032 Merge pull request #215 from DSheirer/198-audio-recorder-handles-left-open
Resolves issue audio recorders left open
2017-03-03 10:58:08 +00:00
Denny
f51ae0b5da Resolves issue with periodic instances of audio recorders not being shutdown correctly. Adds recorder monitor as a fail-safe to automatically shutdown recorders that don't receive an end-call indication. 2017-03-03 05:56:32 -05:00
Denny Sheirer
561078e9bf Merge pull request #214 from DSheirer/210-playlist-saving-all-times
Resolves issue with the playlist saving all of the time
2017-03-03 00:34:25 +00:00
Denny
e052b01f9b Resolves issue with the playlist saving all of the time due to audio streaming broadcast events. 2017-03-02 19:33:10 -05:00
Denny
09cf4fcfd7 Resolves issue with empty string list models in channel editor 2017-02-26 10:29:14 -05:00
Denny
5e731d0e9f Resolves issue with alias list and group combo box value editing. 2017-02-26 06:51:58 -05:00
Denny Sheirer
670a02b0ba Merge pull request #209 from DSheirer/153-cursor-frequency-artifacts
Resolves issue with spectral display rendering artifacts
2017-02-26 10:48:59 +00:00
Denny
9ef156b061 Resolves issue with spectral display rendering artifacts from cursor frequency readout when accessing menu items directly above the spectral display. 2017-02-26 05:47:26 -05:00
Denny
d9cf1b19f3 Removes assert() usage throughout project and replaces with Validate from apache commons. 2017-02-25 16:03:35 -05:00
Denny
a4a339b505 Resolves issue with FCD2 tuner frequency. 2017-02-25 14:58:51 -05:00
Denny
119370c46e Resolves issue with FCD2 tuner frequency. 2017-02-25 14:57:25 -05:00
Denny
4eb66e6045 Updated readme file with screen shot and link to new Version 0.3.0 user's manual. 2017-02-25 06:18:59 -05:00
Denny Sheirer
8827ecd0e9 Merge pull request #207 from DSheirer/206-alias-list-npe-null-list-name
206 alias list npe null list name
2017-02-24 10:25:29 +00:00
Denny
8660002f93 Incrementing build to beta 6 2017-02-24 05:24:38 -05:00
Denny
2058fcbc1d Resolves issue #206 - alias list throws NPE on aliases that don't have a list name specified. 2017-02-24 05:23:48 -05:00
Denny
9515dfa380 Incrementing build for 0.3.0 beta #5 2017-02-20 20:25:43 -05:00
Denny
9e9a604c6a Merge remote-tracking branch 'origin/master' 2017-02-20 20:20:11 -05:00
Denny
234e313b7a Resolves issue with Null Pointer exception in alias list 2017-02-20 20:19:47 -05:00
Denny Sheirer
e1e7169029 Merge pull request #203 from DSheirer/199-utf8-encoding-of-metadata
Resolves issue with UTF-8 encoding of Shoutcast 2 metadata updates.
2017-02-17 23:09:10 +00:00
Denny
f3605db0ed Resolves issue where metadata is UTF-8 URL encoded versus UTF-8 String encoding. 2017-02-17 18:08:01 -05:00
Denny Sheirer
c0a6d578c7 Merge pull request #202 from DSheirer/201-audio-stream-failed-reconnect
Resolves issue with audio broadcasters attempting to reconnect if a c…
2017-02-17 22:54:58 +00:00
Denny
79b4b30ec7 Resolves issue with audio broadcasters attempting to reconnect if a connection attempt is not successful. 2017-02-17 17:53:42 -05:00
Denny
17c7466c6b Resolves logic issue in Icecast error handling during resets. 2017-02-13 20:51:10 -05:00
Denny Sheirer
8162d6ffd6 Merge pull request #196 from DSheirer/164-patched-group-streaming-duplicates
Resolves #173 - duplicate streaming calls for patch groups.
2017-02-12 15:59:46 +00:00
Denny
cd60251337 Resolves #173 - duplicate streaming calls for patch groups. 2017-02-12 10:59:06 -05:00
Denny Sheirer
5647879b37 Merge pull request #195 from DSheirer/173-p25-tdma-channel-frequencies-incorrect
Resolves #173 - logging/management of  P25 TDMA channels.
2017-02-12 15:21:50 +00:00
Denny
6916d95cdf Resolves #173 - logging/management of P25 TDMA channels. There is no Phase II decoder support yet. 2017-02-12 10:20:45 -05:00
Denny Sheirer
a0c32f6fe0 Merge pull request #194 from DSheirer/193-icecast-tcp-write-timeout-exception
Resolves #193 - adds handling for MINA WriteTimeoutException
2017-02-12 13:05:11 +00:00
Denny
c3d60dc174 Resolves #193 - adds handling for MINA WriteTimeoutException error. Resets connection when this occurs. 2017-02-12 08:04:27 -05:00
Denny Sheirer
e55a2226a2 Merge pull request #192 from DSheirer/189-shoutcast-ultravox-UTF8-encoding
Resolves #189 - add UTF-8 encoding to shoutcast v2
2017-02-12 12:45:10 +00:00
Denny
9f01678b00 Resolves #189 - add UTF-8 encoding to shoutcast v2 (ultravox) XML metadata update values. 2017-02-12 07:43:51 -05:00
Denny Sheirer
46425e9cae Merge pull request #191 from DSheirer/188-p25-activity-summary-cleanup
188 p25 activity summary cleanup
2017-02-12 12:31:45 +00:00
Denny
3726401563 Resolves #188 - cleanup of the P25 and MPT activity summaries. Resolves an issue in the SNDCP downlink/uplink channel frequencies. 2017-02-12 07:30:32 -05:00
Denny
a3e50acfe3 Incrementing build to 0.3.0-beta4 2017-02-11 09:02:35 -05:00
Denny
cf0018e6ba Incrementing build to 0.3.0-beta4 2017-02-11 07:22:54 -05:00
Denny Sheirer
e8055dcab0 Merge pull request #187 from DSheirer/186-p25-c4fm-decoder-no-worky
Resolves #186 - p25 C4FM decoder wasn't working (at all).
2017-02-11 12:17:58 +00:00
Denny
fce3d5c4f3 Resolves #186 - p25 C4FM decoder wasn't working (at all). 2017-02-11 07:17:23 -05:00
Denny Sheirer
9cc0d367a8 Merge pull request #185 from DSheirer/183-alias-list-throws-npe-on-empty-name
Resolves #183 - alias list throws NPE when created with a null or emp…
2017-02-11 00:35:16 +00:00
Denny
2ca9e5d5ce Resolves #183 - alias list throws NPE when created with a null or empty name and receives alias updates. 2017-02-10 19:34:50 -05:00
Denny Sheirer
861203c3a0 Merge pull request #184 from DSheirer/181-patch-group-audio-priority
Resolves #181 - patch group audio priority will assume the highest (l…
2017-02-11 00:31:42 +00:00
Denny
7c930deefe Resolves #181 - patch group audio priority will assume the highest (listenable) audio priority and override a 'Do Not Monitor' setting if any of the other aliases have an audio priority defined. 2017-02-10 19:31:09 -05:00
Denny
0e0af932eb Incrementing build to 0.3.0-beta3 2017-02-10 17:09:54 -05:00
Denny Sheirer
6ba5e91fcb Merge pull request #182 from DSheirer/176-streaming-handle-additional-io-exceptions
Resolves #176 - resolves 2 streaming related issues
2017-02-10 22:06:32 +00:00
Denny
34dd2161ef Resolves #176 - 2 issues:
A) streaming connection errors - detects errors and if currently connected, resets the connection.

B)duplicate calls being streamed - issue with comparison of broadcast channels allowed the same broadcast channel to be queued two or more times for the list of channels to stream a recording.
2017-02-10 17:05:28 -05:00
Denny Sheirer
2543072e08 Merge pull request #180 from DSheirer/166-traffic_channel_manager_cleanup
Resolves #166 - removes traffic channel timeout from traffic channel manager
2017-02-10 15:31:24 +00:00
Denny
281be147f8 Resolves #166 - removes traffic channel timeout from traffic channel manager 2017-02-10 10:30:35 -05:00
Denny Sheirer
12ecca4555 Merge pull request #179 from DSheirer/169-aarch64-usb-support-for-ARM64
Resolves #165 - adds ARM64 (aarch64) linux libusb support
2017-02-10 15:26:16 +00:00
Denny
4c27d04120 Resolves #165 - adds ARM64 (aarch64) linux libusb support 2017-02-10 10:08:52 -05:00
Denny Sheirer
ece202315f Merge pull request #178 from DSheirer/165-overflowable-buffer-issue-causes-channels-decoding-stop
165 overflowable buffer issue causes channels decoding stop
2017-02-10 14:50:30 +00:00
Denny Sheirer
6afe5dcec9 Merge pull request #177 from DSheirer/master
165 - P25 Channel stops decoding
2017-02-10 14:47:33 +00:00
Denny
8db47c25bc #165 - cleanup of logging and comments in TunerChannelSource 2017-02-10 09:45:32 -05:00
Denny
6a340050c6 #165 - cleanup of logging and comments in TunerChannelSource 2017-02-10 06:41:45 -05:00
Denny
183eb2f7c7 Resolves #165 - P25 decoding channel stops arbitrarily and never recovers.
Issue identified with Patch Group management and circular dependencies that can develop because of stale patch groups.  Refactored all patch group management into a new PatchGroupManager.  Patch groups are now automatically deleted if we don't receive any update messages within 5 seconds.  The PatchGroupAlias was also updated to not roll-up attributes from subordinate patched group aliases when those aliases are also patch group aliases -- this eliminates the circular dependency chaining.

Added IOverflowListener interface and updated Sources to support overflow listeners.  Updated ChannelState to register as an overflow listener so that it can visually depict to the user when the channel enters/exits source overflow state.  Added new color pattern for OVERFLOW with yellow text on red.
2017-02-10 06:36:57 -05:00
Denny Sheirer
6bc92e8b94 Merge pull request #174 from RachelOlivero/gui_accessibility_enhancements
Add a Text Label to the Mute Image Button
2017-02-04 20:21:44 +00:00
Denny
767b302ed7 Adds additional unresolved address exception handler to metadata updater 2017-02-04 07:32:22 -05:00
Rachel Olivero
952d930b5b Adds a screen reader accessible label to the mute button. Label toggles between mute and unmute to match the action that will be taken when the button is next pressed. 2017-02-03 10:19:52 -05:00
Denny
26087c465f Adds additional unresolved address exception handler to metadata updater 2017-01-29 12:37:09 -05:00
Denny
74c15d530b Resolves #164 - USB time out while processing buffer transfers. Decreases periodicity of processing the LibUsb timeout handler from 9 milliseconds to 5 milliseconds to reduce chance that buffers are not being serviced in time. 2017-01-29 11:02:12 -05:00
165 changed files with 13095 additions and 10061 deletions

View File

@@ -7,6 +7,7 @@
<w>fleetsync</w>
<w>icecast</w>
<w>ident</w>
<w>overflowable</w>
<w>polyphase</w>
<w>remez</w>
<w>samplerate</w>
@@ -17,6 +18,7 @@
<w>talkgroup</w>
<w>tgid</w>
<w>ultravox</w>
<w>wacn</w>
<w>xtea</w>
</words>
</dictionary>

View File

@@ -27,39 +27,9 @@
<root url="jar://$PROJECT_DIR$/imports/commons-codec-1.9.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-logging-1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/slf4j-api-1.7.21.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-aarch64.jar!/" />
</CLASSES>
<JAVADOC>
<root url="https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/index.html" />
</JAVADOC>
<SOURCES>
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-osgi/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-osgi/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/test/java" />
</SOURCES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,10 +1,18 @@
0.3.2 20171105
- Resolves issue with tuner frequency adjustments that was causing channel processing
to lockup and show persistent 'overflow' state in each channel status
- Resolves issue with broadcastify 'mountpoint-in-use' error handling and updates
memory usage logging
0.3.0-beta1 20170129
0.3.1 20171009
- Resolves Java 9 incompatibility issues
0.3.0 20170922
- REQUIRES: Java 8 (or newer) for compatibility and eventual changeover to JavaFX
- FEATURE: audio streaming - adds capability to stream to multiple remote broadcast
servers (Broadcastify, Icecast v2.3 and v2.4+, and Shoutcast V1 and V2). Each
stream can be independently configured with a delay. Audio calls are directed
to one or more streams by attaching one or more stream identifiers to each alias.
- Requires Java 8 (or newer) for compatibility and eventual changeover to JavaFX
- Extensive GUI updates for tuner, channel and alias management. Includes new
'Now Playing' channel view and 'Detail' view.
- Updated alias list management of alias id values and wildcards. Weighting is now
@@ -17,7 +25,8 @@
- Implemented bounded queues for both tuner and individual channel IQ sample
buffers. Bounded queues have ability to enter/exit overflow state when queue
size reaches overflow threshold and reset when queue size falls below the reset
threshold.
threshold. Channel state will show OVERFLOW in red/yellow.
- Improves handling of P25 TDMA channels (NO Phase 2 decoder support yet)
- Updated airspy controller for airspy-mini specific sample rate handling.
- New developer remez filter designer tool and internal usage

View File

@@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -2,18 +2,9 @@
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
* [User's Manual Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/UserManual_Version0.3.0)
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
![](https://github.com/DSheirer/sdrtrunk/wiki/images/SDRTrunk_application.png)
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-67489234-1', 'auto');
ga('send', 'pageview');
</script>
![sdrtrunk Application Overview - Version 0.3.0](https://github.com/DSheirer/sdrtrunk/wiki/images/ApplicationOverview_V0.3.0.png)
**Figure 1:** sdrtrunk **Version 0.3.0** Application Screenshot

View File

@@ -1,121 +1,142 @@
<project name="SDRTrunk" default="SDRTrunk" basedir=".">
<description>
Ant build script for SDRTrunk Decoder Application
</description>
<description>
Ant build script for SDRTrunk Decoder Application
</description>
<!-- version properties -->
<property name="major" value="0"/>
<property name="minor" value="3"/>
<property name="patch" value="0"/>
<property name="release" value="-beta2"/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<!-- Build properties -->
<property name="repo" location=".."/>
<property name="source.code" location="${repo}/src"/>
<property name="source.configs" location="${repo}/config"/>
<property name="source.images" location="${repo}/images"/>
<property name="source.libs" location="${repo}/imports"/>
<property name="source.license" location="${repo}/license"/>
<property name="source.scripts" location="${repo}/scripts"/>
<!-- version properties -->
<property name="major" value="0"/>
<property name="minor" value="3"/>
<property name="patch" value="1"/>
<property name="release" value=""/> <!-- Use dash (-) prefix (e.g. -beta1) -->
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<property name="output" location="${repo}/product"/>
<property name="output.classes" location="${repo}/classes"/>
<property name="output.folder" location="${output}/sdrtrunk"/>
<property name="output.config" location="${output.folder}/config"/>
<property name="output.images" location="${output.folder}/images"/>
<property name="output.libs" location="${output.folder}/libs"/>
<!-- Build properties -->
<property name="repo" location=".."/>
<property name="source.code" location="${repo}/src"/>
<property name="source.configs" location="${repo}/config"/>
<property name="source.images" location="${repo}/images"/>
<property name="source.libs" location="${repo}/imports"/>
<property name="source.scripts" location="${repo}/scripts"/>
<path id="classpath">
<fileset dir="${source.libs}" includes="*.jar" />
</path>
<target name="init">
<!-- Create the classes folder to contain the compiled java classes -->
<mkdir dir="${output.classes}"/>
<!-- Create version file -->
<echo message="${major}.${minor}.${patch}-${release}" file="${output.classes}/sdrtrunk-version"/>
</target>
<property name="output" location="${repo}/product"/>
<property name="output.classes" location="${repo}/classes"/>
<property name="output.folder" location="${output}/sdrtrunk"/>
<property name="output.config" location="${output.folder}/config"/>
<property name="output.images" location="${output.folder}/images"/>
<property name="output.libs" location="${output.folder}/libs"/>
<target name="compile" depends="clean,init" description="Compile java classes" >
<javac srcdir="${source.code}" destdir="${output.classes}" classpathref="classpath" includeantruntime="false" />
</target>
<path id="classpath">
<fileset dir="${source.libs}" includes="*.jar"/>
</path>
<target name="SDRTrunk" depends="compile" description="Create SDRTrunk application" >
<!-- Create the distribution directory -->
<mkdir dir="${output.folder}"/>
<target name="init">
<!-- Create the classes folder to contain the compiled java classes -->
<mkdir dir="${output.classes}"/>
<!-- Create jar file -->
<jar jarfile="${output.folder}/sdrtrunk.jar" basedir="${output.classes}">
<manifest>
<attribute name="Title" value="sdrtrunk"/>
<attribute name="Main-Class" value="gui.SDRTrunk"/>
<attribute name="Version" value="${major}.${minor}.${patch}-${release}"/>
</manifest>
<fileset dir="${source.configs}">
<exclude name="*.rules"/>
</fileset>
<fileset dir="${source.license}">
<include name="license.txt"/>
</fileset>
</jar>
<!-- Copy third-party libraries -->
<mkdir dir="${output.libs}"/>
<copy todir="${output.libs}">
<fileset dir="${source.libs}" includes="*.jar" />
</copy>
<!-- Copy config files -->
<mkdir dir="${output.config}"/>
<!-- Create version file -->
<echo message="${major}.${minor}.${patch}${release}" file="${output.classes}/sdrtrunk-version"/>
</target>
<copy todir="${output.config}">
<fileset dir="${source.configs}">
<include name="*.rules"/>
</fileset>
</copy>
<!-- Copy image files -->
<mkdir dir="${output.images}"/>
<copy todir="${output.images}">
<fileset dir="${source.images}" />
</copy>
<!-- Copy license file -->
<copy todir="${output.folder}">
<fileset dir="${source.license}">
<include name="license.txt"/>
</fileset>
</copy>
<!-- Copy program scripts -->
<copy todir="${output.folder}">
<fileset dir="${source.scripts}" />
</copy>
<target name="compile" depends="clean,init,compile-java-8,compile-java-9"
description="Compile java classes" />
<target name="compile-java-8" depends="check-java-version" if="java.8" description="Compiles code with Java 8 arguments">
<echo message="Compiling with Java 8 Options"/>
<javac srcdir="${source.code}" destdir="${output.classes}" classpathref="classpath" includeantruntime="false"/>
</target>
<target name="compile-java-9" depends="check-java-version" if="java.9" description="Compiles code with Java 9 arguments">
<echo message="Compiling with Java 9 Options"/>
<javac srcdir="${source.code}" destdir="${output.classes}" classpathref="classpath" includeantruntime="false">
<compilerarg value="--add-modules"/>
<compilerarg value="java.xml.bind"/>
</javac>
</target>
<target name="check-java-version" description="Determines if the compiler is Java 8 or 9">
<condition property="java.8" value="true">
<contains string="${java.version}" substring="1.8"/>
</condition>
<condition property="java.9" value="true">
<equals arg1="${java.version}" arg2="9"/>
</condition>
<echo message="Java Version: ${java.version}"/>
</target>
<target name="SDRTrunk" depends="compile" description="Create SDRTrunk application">
<!-- Create the distribution directory -->
<mkdir dir="${output.folder}"/>
<!-- Create jar file -->
<jar jarfile="${output.folder}/sdrtrunk.jar" basedir="${output.classes}">
<manifest>
<attribute name="Title" value="sdrtrunk"/>
<attribute name="Main-Class" value="gui.SDRTrunk"/>
<attribute name="Version" value="${major}.${minor}.${patch}-${release}"/>
</manifest>
<fileset dir="${source.configs}">
<exclude name="*.rules"/>
</fileset>
<fileset dir="${repo}">
<include name="LICENSE"/>
</fileset>
</jar>
<!-- Copy third-party libraries -->
<mkdir dir="${output.libs}"/>
<copy todir="${output.libs}">
<fileset dir="${source.libs}" includes="*.jar"/>
</copy>
<!-- Copy config files -->
<mkdir dir="${output.config}"/>
<copy todir="${output.config}">
<fileset dir="${source.configs}">
<include name="*.rules"/>
</fileset>
</copy>
<!-- Copy image files -->
<mkdir dir="${output.images}"/>
<copy todir="${output.images}">
<fileset dir="${source.images}"/>
</copy>
<!-- Copy license file -->
<copy todir="${output.folder}">
<fileset dir="${repo}">
<include name="LICENSE"/>
</fileset>
</copy>
<!-- Copy program scripts -->
<copy todir="${output.folder}">
<fileset dir="${source.scripts}"/>
</copy>
<!-- Bundle the files into .zip -->
<zip destfile="${output}/${filename}.zip" basedir="${output}" />
<!-- Bundle the files into .zip -->
<zip destfile="${output}/${filename}.zip" basedir="${output}"/>
<!-- Remove linux start script before tar task -->
<delete file="${output.folder}/run_sdrtrunk_linux.sh"/>
<!-- Remove linux start script before tar task -->
<delete file="${output.folder}/run_sdrtrunk_linux.sh"/>
<!-- Bundle files into .tar.gz and copy/set permissions on linux start script -->
<tar destfile="${output}/${filename}.tar.gz" compression="gzip">
<tarfileset dir="${output.folder}" prefix="sdrtrunk" />
<tarfileset dir="${source.scripts}" prefix="sdrtrunk" filemode="777">
<include name="run_sdrtrunk_linux.sh"/>
</tarfileset>
</tar>
<delete dir="${output.folder}"/>
</target>
<!-- Bundle files into .tar.gz and copy/set permissions on linux start script -->
<tar destfile="${output}/${filename}.tar.gz" compression="gzip">
<tarfileset dir="${output.folder}" prefix="sdrtrunk"/>
<tarfileset dir="${source.scripts}" prefix="sdrtrunk" filemode="777">
<include name="run_sdrtrunk_linux.sh"/>
</tarfileset>
</tar>
<target name="clean" description="Clean previous build artifacts" >
<delete dir="${output.classes}"/>
<delete dir="${output}"/>
</target>
<delete dir="${output.folder}"/>
</target>
<target name="clean" description="Clean previous build artifacts">
<delete dir="${output.classes}"/>
<delete dir="${output}"/>
</target>
</project>

View File

@@ -1,4 +1,6 @@
<configuration>
<conversionRule conversionWord="memory_usage" converterClass="util.MemoryUsageLogger" />
<timestamp key="appstart" datePattern="HHmmss"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
@@ -9,7 +11,7 @@
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-25(%d{yyyyMMdd HHmmss.SSS} [%thread]) %-5level %logger{30} - %msg%n</pattern>
<pattern>%-25(%d{yyyyMMdd HHmmss.SSS} [%thread]) %-5level %logger{30} - %msg %memory_usage%n</pattern>
</encoder>
</appender>
@@ -19,7 +21,7 @@
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-12(%d{HH:mm:ss.SSS}) %-5level %logger{25} - %msg%n</pattern>
<pattern>%-12(%d{HH:mm:ss.SSS}) %-5level %logger{25} - %msg %memory_usage%n</pattern>
</encoder>
</appender>

Binary file not shown.

View File

@@ -1,2 +1,16 @@
#!/bin/bash
java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk
java -version
# Test for java version 9
JAVA_9=$(java -version 2>&1 | grep -i version | sed 's/.* "\([9]\).*/\1/')
if [ -z ${JAVA_9+x} ]; then
echo Using Java 9 program options
# Java 9 uses the G1 garbage collector by default and requires that we specify the
# java.xml.bind package since it's been removed from the java se core library
java -cp "*:libs/*" --add-modules java.xml.bind gui.SDRTrunk;
else
echo Using Java 8 program options
java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk;
fi

View File

@@ -1 +1,25 @@
java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
@echo off
java -version
SETLOCAL ENABLEDELAYEDEXPANSION
for /f "usebackq tokens=3" %%a in (`java -version 2^>^&1 ^| find "version"`) do (
rem set b to the unquoted version string parsed from the java version command
set b=%%~a
rem set c to the first character of the unquoted version string
set c=!b:~0,1!
if !c! equ 9 (
echo Using Java 9 options
java -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" --add-modules java.xml.bind gui.SDRTrunk
) else (
echo Using Java 8 options
java -XX:+UseG1GC -cp "SDRTrunk.jar;*;libs/*;config/*;images/*" gui.SDRTrunk
)
)
ENDLOCAL
@echo on

View File

@@ -185,27 +185,6 @@ public class Alias
*/
public void validate()
{
//Check for duplicate broadcast channels
List<BroadcastChannel> broadcastChannels = getBroadcastChannels();
if(broadcastChannels.size() > 0)
{
Set<String> channelNames = new TreeSet<>();
for(BroadcastChannel broadcastChannel: broadcastChannels)
{
String channelName = broadcastChannel.getChannelName();
if(channelName == null || channelName.isEmpty() || channelNames.contains(channelName))
{
removeAliasID(broadcastChannel);
}
else
{
channelNames.add(channelName);
}
}
}
}
/**
* Returns the priority level of this alias, if defined, or the default priority
@@ -336,9 +315,9 @@ public class Alias
* List of broadcast channels specified for this alias.
*/
@XmlTransient
public List<BroadcastChannel> getBroadcastChannels()
public Set<BroadcastChannel> getBroadcastChannels()
{
List<BroadcastChannel> broadcastChannels = new ArrayList<>();
Set<BroadcastChannel> broadcastChannels = new TreeSet<>();
for(AliasID id: getId())
{

View File

@@ -631,36 +631,47 @@ public class AliasList implements Listener<AliasEvent>
return mName;
}
/**
* Indicates if this alias list has a non-null, non-empty name
*/
private boolean hasName()
{
return mName != null && !mName.isEmpty();
}
/**
* Receive alias change event notifications and modify this list accordingly
*/
@Override
public void receive(AliasEvent event)
{
Alias alias = event.getAlias();
switch(event.getEvent())
if(hasName())
{
case ADD:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
addAlias(alias);
}
break;
case CHANGE:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
addAlias(alias);
}
break;
case DELETE:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
removeAlias(alias);
}
break;
default:
break;
Alias alias = event.getAlias();
switch(event.getEvent())
{
case ADD:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
addAlias(alias);
}
break;
case CHANGE:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
addAlias(alias);
}
break;
case DELETE:
if(alias.getList() != null && getName().equalsIgnoreCase(alias.getList()))
{
removeAlias(alias);
}
break;
default:
break;
}
}
}
}

View File

@@ -1,22 +1,28 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
*
* This 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 alias;
import alias.AliasEvent.Event;
import alias.id.broadcast.BroadcastChannel;
import sample.Broadcaster;
import sample.Listener;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -24,195 +30,192 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.table.AbstractTableModel;
import alias.id.broadcast.BroadcastChannel;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasEvent.Event;
/**
* Alias Model
*/
public class AliasModel extends AbstractTableModel
{
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
public static final int COLUMN_LIST = 0;
public static final int COLUMN_GROUP = 1;
public static final int COLUMN_NAME = 2;
public static final int COLUMN_ICON = 3;
public static final int COLUMN_COLOR = 4;
public static final int COLUMN_LIST = 0;
public static final int COLUMN_GROUP = 1;
public static final int COLUMN_NAME = 2;
public static final int COLUMN_ICON = 3;
public static final int COLUMN_COLOR = 4;
private List<Alias> mAliases = new CopyOnWriteArrayList<>();
private Broadcaster<AliasEvent> mAliasEventBroadcaster = new Broadcaster<>();
private Map<String,AliasList> mAliasListMap = new HashMap<>();
private List<Alias> mAliases = new CopyOnWriteArrayList<>();
private Broadcaster<AliasEvent> mAliasEventBroadcaster = new Broadcaster<>();
private Map<String,AliasList> mAliasListMap = new HashMap<>();
public AliasModel()
{
}
public AliasModel()
{
}
/**
* Unmodifiable list of all aliases currently in the model
*/
public List<Alias> getAliases()
{
return Collections.unmodifiableList( mAliases );
}
public Alias getAliasAtIndex( int row )
{
if( mAliases.size() >= row )
{
return mAliases.get( row );
}
return null;
}
public AliasList getAliasList( String name )
{
if( mAliasListMap.containsKey( name ) )
{
return mAliasListMap.get( name );
}
AliasList aliasList = new AliasList( name );
for( Alias alias: mAliases )
{
if( alias.getList().equalsIgnoreCase( name ) )
{
aliasList.addAlias( alias );
}
}
/**
* Unmodifiable list of all aliases currently in the model
*/
public List<Alias> getAliases()
{
return Collections.unmodifiableList(mAliases);
}
mAliasListMap.put( name, aliasList );
public Alias getAliasAtIndex(int row)
{
if(mAliases.size() >= row)
{
return mAliases.get(row);
}
//Register the new alias list to receive updates from this model
addListener( aliasList );
return aliasList;
}
/**
* Returns a list of unique alias list names from across the alias set
*/
public List<String> getListNames()
{
List<String> listNames = new ArrayList<>();
for( Alias alias: mAliases )
{
if( alias.hasList() && !listNames.contains( alias.getList() ) )
{
listNames.add( alias.getList() );
}
}
Collections.sort( listNames );
return listNames;
}
/**
* Returns a list of alias group names for all aliases
*/
public List<String> getGroupNames()
{
List<String> groupNames = new ArrayList<>();
return null;
}
for( Alias alias: mAliases )
{
if( alias.hasGroup() && !groupNames.contains( alias.getGroup() ) )
{
groupNames.add( alias.getGroup() );
}
}
public AliasList getAliasList(String name)
{
if(name != null && mAliasListMap.containsKey(name))
{
return mAliasListMap.get(name);
}
Collections.sort( groupNames );
return groupNames;
}
AliasList aliasList = new AliasList(name);
/**
* Returns a list of alias group names for all aliases that have a matching
* list name value
*/
public List<String> getGroupNames( String listName )
{
List<String> groupNames = new ArrayList<>();
if(name != null)
{
for(Alias alias : mAliases)
{
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
{
aliasList.addAlias(alias);
}
}
if( listName != null )
{
for( Alias alias: mAliases )
{
if( alias.hasList() &&
alias.hasGroup() &&
listName.equals( alias.getList() ) &&
!groupNames.contains( alias.getGroup() ) )
{
groupNames.add( alias.getGroup() );
}
}
}
mAliasListMap.put(name, aliasList);
Collections.sort( groupNames );
return groupNames;
}
//Register the new alias list to receive updates from this model
addListener(aliasList);
}
/**
* Bulk loading of aliases
*/
public void addAliases( List<Alias> aliases )
{
for( Alias alias: aliases )
{
addAlias( alias );
}
}
return aliasList;
}
/**
* Adds the alias to the model
*/
public int addAlias( Alias alias )
{
if( alias != null )
{
mAliases.add( alias );
/**
* Returns a list of unique alias list names from across the alias set
*/
public List<String> getListNames()
{
List<String> listNames = new ArrayList<>();
int index = mAliases.size() - 1;
for(Alias alias : mAliases)
{
if(alias.hasList() && !listNames.contains(alias.getList()))
{
listNames.add(alias.getList());
}
}
fireTableRowsInserted( index, index );
broadcast( new AliasEvent( alias, Event.ADD ) );
Collections.sort(listNames);
return index;
}
return -1;
}
/**
* Removes the channel from the model and broadcasts a channel remove event
*/
public void removeAlias( Alias alias )
{
if( alias != null )
{
int index = mAliases.indexOf( alias );
mAliases.remove( alias );
fireTableRowsDeleted( index, index );
broadcast( new AliasEvent( alias, Event.DELETE ) );
}
}
return listNames;
}
/**
* Returns a list of alias group names for all aliases
*/
public List<String> getGroupNames()
{
List<String> groupNames = new ArrayList<>();
for(Alias alias : mAliases)
{
if(alias.hasGroup() && !groupNames.contains(alias.getGroup()))
{
groupNames.add(alias.getGroup());
}
}
Collections.sort(groupNames);
return groupNames;
}
/**
* Returns a list of alias group names for all aliases that have a matching
* list name value
*/
public List<String> getGroupNames(String listName)
{
List<String> groupNames = new ArrayList<>();
if(listName != null)
{
for(Alias alias : mAliases)
{
if(alias.hasList() &&
alias.hasGroup() &&
listName.equals(alias.getList()) &&
!groupNames.contains(alias.getGroup()))
{
groupNames.add(alias.getGroup());
}
}
}
Collections.sort(groupNames);
return groupNames;
}
/**
* Bulk loading of aliases
*/
public void addAliases(List<Alias> aliases)
{
for(Alias alias : aliases)
{
addAlias(alias);
}
}
/**
* Adds the alias to the model
*/
public int addAlias(Alias alias)
{
if(alias != null)
{
mAliases.add(alias);
int index = mAliases.size() - 1;
fireTableRowsInserted(index, index);
broadcast(new AliasEvent(alias, Event.ADD));
return index;
}
return -1;
}
/**
* Removes the channel from the model and broadcasts a channel remove event
*/
public void removeAlias(Alias alias)
{
if(alias != null)
{
int index = mAliases.indexOf(alias);
mAliases.remove(alias);
fireTableRowsDeleted(index, index);
broadcast(new AliasEvent(alias, Event.DELETE));
}
}
/**
* Renames any broadcast channels that have the previous name.
*
* @param previousName to rename
* @param newName to assign to the broadcast channel
*/
@@ -223,11 +226,11 @@ public class AliasModel extends AbstractTableModel
return;
}
for(Alias alias: mAliases)
for(Alias alias : mAliases)
{
if(alias.hasBroadcastChannel(previousName))
{
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
for(BroadcastChannel broadcastChannel : alias.getBroadcastChannels())
{
if(broadcastChannel.getChannelName().contentEquals(previousName))
{
@@ -238,85 +241,85 @@ public class AliasModel extends AbstractTableModel
}
}
@Override
public int getRowCount()
{
return mAliases.size();
}
@Override
public int getRowCount()
{
return mAliases.size();
}
@Override
public int getColumnCount()
{
return 5;
}
@Override
public int getColumnCount()
{
return 5;
}
@Override
public String getColumnName( int columnIndex )
{
switch( columnIndex )
{
case COLUMN_LIST:
return "List";
case COLUMN_GROUP:
return "Group";
case COLUMN_NAME:
return "Name";
case COLUMN_ICON:
return "Icon";
case COLUMN_COLOR:
return "Color";
}
return null;
}
@Override
public String getColumnName(int columnIndex)
{
switch(columnIndex)
{
case COLUMN_LIST:
return "List";
case COLUMN_GROUP:
return "Group";
case COLUMN_NAME:
return "Name";
case COLUMN_ICON:
return "Icon";
case COLUMN_COLOR:
return "Color";
}
@Override
public Class<?> getColumnClass( int columnIndex )
{
if( columnIndex == COLUMN_COLOR )
{
return Integer.class;
}
return String.class;
}
return null;
}
@Override
public boolean isCellEditable( int rowIndex, int columnIndex )
{
return false;
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
if(columnIndex == COLUMN_COLOR)
{
return Integer.class;
}
@Override
public Object getValueAt( int rowIndex, int columnIndex )
{
Alias alias = mAliases.get( rowIndex );
switch( columnIndex )
{
case COLUMN_LIST:
return alias.getList();
case COLUMN_GROUP:
return alias.getGroup();
case COLUMN_NAME:
return alias.getName();
case COLUMN_ICON:
return alias.getIconName();
case COLUMN_COLOR:
return alias.getColor();
}
return null;
}
return String.class;
}
@Override
public void setValueAt( Object aValue, int rowIndex, int columnIndex )
{
throw new IllegalArgumentException( "Not yet implemented" );
}
public void broadcast( AliasEvent event )
{
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
Alias alias = mAliases.get(rowIndex);
switch(columnIndex)
{
case COLUMN_LIST:
return alias.getList();
case COLUMN_GROUP:
return alias.getGroup();
case COLUMN_NAME:
return alias.getName();
case COLUMN_ICON:
return alias.getIconName();
case COLUMN_COLOR:
return alias.getColor();
}
return null;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new IllegalArgumentException("Not yet implemented");
}
public void broadcast(AliasEvent event)
{
Alias alias = event.getAlias();
//Validate the alias following a user action that changed the alias or any alias IDs
@@ -325,23 +328,23 @@ public class AliasModel extends AbstractTableModel
alias.validate();
}
if( event.getEvent() == Event.CHANGE )
{
int index = mAliases.indexOf( event.getAlias() );
fireTableRowsUpdated( index, index );
}
mAliasEventBroadcaster.broadcast( event );
}
public void addListener( Listener<AliasEvent> listener )
{
mAliasEventBroadcaster.addListener( listener );
}
public void removeListener( Listener<AliasEvent> listener )
{
mAliasEventBroadcaster.removeListener( listener );
}
if(event.getEvent() == Event.CHANGE)
{
int index = mAliases.indexOf(event.getAlias());
fireTableRowsUpdated(index, index);
}
mAliasEventBroadcaster.broadcast(event);
}
public void addListener(Listener<AliasEvent> listener)
{
mAliasEventBroadcaster.addListener(listener);
}
public void removeListener(Listener<AliasEvent> listener)
{
mAliasEventBroadcaster.removeListener(listener);
}
}

View File

@@ -40,10 +40,11 @@ public class AliasNameEditor extends Editor<Alias>
{
private static final long serialVersionUID = 1L;
private static ComboBoxModel<String> EMPTY_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_LIST_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_GROUP_MODEL = new DefaultComboBoxModel<>();
private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_MODEL);
private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_MODEL);
private JComboBox<String> mListCombo = new JComboBox<>(EMPTY_LIST_MODEL);
private JComboBox<String> mGroupCombo = new JComboBox<>(EMPTY_GROUP_MODEL);
private JComboBox<Icon> mIconCombo;
private JTextField mName;
private JButton mButtonColor;
@@ -110,7 +111,7 @@ public class AliasNameEditor extends Editor<Alias>
if(groups.isEmpty())
{
mGroupCombo.setModel(EMPTY_MODEL);
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
}
else
{
@@ -238,7 +239,7 @@ public class AliasNameEditor extends Editor<Alias>
if(listNames.isEmpty())
{
mListCombo.setModel(EMPTY_MODEL);
mListCombo.setModel(EMPTY_LIST_MODEL);
}
else
{
@@ -253,7 +254,7 @@ public class AliasNameEditor extends Editor<Alias>
if(groupNames.isEmpty())
{
mGroupCombo.setModel(EMPTY_MODEL);
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
}
else
{
@@ -285,8 +286,8 @@ public class AliasNameEditor extends Editor<Alias>
}
else
{
mListCombo.setModel(EMPTY_MODEL);
mGroupCombo.setModel(EMPTY_MODEL);
mListCombo.setModel(EMPTY_LIST_MODEL);
mGroupCombo.setModel(EMPTY_GROUP_MODEL);
mName.setText(null);
mButtonColor.setBackground(getBackground());

View File

@@ -24,12 +24,20 @@ import alias.id.AliasIDType;
import alias.id.broadcast.BroadcastChannel;
import alias.id.priority.Priority;
import alias.id.talkgroup.TalkgroupID;
import gui.SDRTrunk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import playlist.version1.System;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class PatchGroupAlias extends Alias
{
private final static Logger mLog = LoggerFactory.getLogger(PatchGroupAlias.class);
private Alias mPatchGroupAlias;
private List<Alias> mPatchedAliases = new ArrayList<>();
private List<String> mPatchedTalkgroupIDs = new ArrayList<>();
@@ -168,11 +176,14 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
for(AliasAction action: alias.getAction())
if(!(alias instanceof PatchGroupAlias))
{
if(!aliasActions.contains(action))
for(AliasAction action: alias.getAction())
{
aliasActions.add(action);
if(!aliasActions.contains(action))
{
aliasActions.add(action);
}
}
}
}
@@ -185,7 +196,7 @@ public class PatchGroupAlias extends Alias
{
for(Alias alias: mPatchedAliases)
{
if(alias.hasActions())
if(!(alias instanceof PatchGroupAlias) && alias.hasActions())
{
return true;
}
@@ -194,20 +205,44 @@ public class PatchGroupAlias extends Alias
return hasPatchGroupAlias() && getPatchGroupAlias().hasActions();
}
/**
* Returns the highest listenable audio priority defined among the aliases in the group, or returns 'Do Not Monitor'
* if that is the only priority defined. Otherwise, returns default (100) priority.
*/
@Override
public int getCallPriority()
{
int highestPriority = (hasPatchGroupAlias() ? getPatchGroupAlias().getCallPriority() : Priority.DEFAULT_PRIORITY);
boolean hasDoNotMonitor = false;
int highestPriority = (hasPatchGroupAlias() ? getPatchGroupAlias().getCallPriority() : Priority.DEFAULT_PRIORITY + 1);
for(Alias alias: mPatchedAliases)
{
if(alias.hasCallPriority() && alias.getCallPriority() < highestPriority)
if(!(alias instanceof PatchGroupAlias) && alias.hasCallPriority() && alias.getCallPriority() < highestPriority)
{
highestPriority = alias.getCallPriority();
if(alias.getCallPriority() == Priority.DO_NOT_MONITOR)
{
hasDoNotMonitor = true;
}
else
{
highestPriority = alias.getCallPriority();
}
}
}
return highestPriority;
if(highestPriority <= Priority.DEFAULT_PRIORITY)
{
return highestPriority;
}
else if(hasDoNotMonitor)
{
return Priority.DO_NOT_MONITOR;
}
else
{
return Priority.DEFAULT_PRIORITY;
}
}
@Override
@@ -220,7 +255,7 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.hasCallPriority())
if(!(alias instanceof PatchGroupAlias) && alias.hasCallPriority())
{
return true;
}
@@ -239,7 +274,7 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.isRecordable())
if(!(alias instanceof PatchGroupAlias) && alias.isRecordable())
{
return true;
}
@@ -258,7 +293,7 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.isStreamable())
if(!(alias instanceof PatchGroupAlias) && alias.isStreamable())
{
return true;
}
@@ -268,9 +303,9 @@ public class PatchGroupAlias extends Alias
}
@Override
public List<BroadcastChannel> getBroadcastChannels()
public Set<BroadcastChannel> getBroadcastChannels()
{
List<BroadcastChannel> broadcastChannels = new ArrayList<>();
Set<BroadcastChannel> broadcastChannels = new TreeSet<>();
if(hasPatchGroupAlias())
{
@@ -279,12 +314,9 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
if(!(alias instanceof PatchGroupAlias))
{
if(!broadcastChannels.contains(broadcastChannel))
{
broadcastChannels.add(broadcastChannel);
}
broadcastChannels.addAll(alias.getBroadcastChannels());
}
}
@@ -301,7 +333,7 @@ public class PatchGroupAlias extends Alias
for(Alias alias: mPatchedAliases)
{
if(alias.hasBroadcastChannel(channel))
if(!(alias instanceof PatchGroupAlias) && alias.hasBroadcastChannel(channel))
{
return true;
}
@@ -309,4 +341,41 @@ public class PatchGroupAlias extends Alias
return false;
}
public static void main(String[] args)
{
Alias a1 = new Alias("Alias 1");
a1.addAliasID(new BroadcastChannel("Channel 1"));
a1.addAliasID(new BroadcastChannel("Channel 2"));
a1.addAliasID(new BroadcastChannel("Channel 3"));
a1.addAliasID(new BroadcastChannel("Channel 4"));
Alias a2 = new Alias("Alias 1");
a1.addAliasID(new BroadcastChannel("Channel 1"));
a1.addAliasID(new BroadcastChannel("Channel 2"));
a1.addAliasID(new BroadcastChannel("Channel 3"));
a1.addAliasID(new BroadcastChannel("Channel 4"));
Alias a3 = new Alias("Alias 1");
a1.addAliasID(new BroadcastChannel("Channel 1"));
a1.addAliasID(new BroadcastChannel("Channel 2"));
a1.addAliasID(new BroadcastChannel("Channel 3"));
a1.addAliasID(new BroadcastChannel("Channel 4"));
Alias a4 = new Alias("Alias 1");
a1.addAliasID(new BroadcastChannel("Channel 1"));
a1.addAliasID(new BroadcastChannel("Channel 2"));
a1.addAliasID(new BroadcastChannel("Channel 3"));
a1.addAliasID(new BroadcastChannel("Channel 4"));
PatchGroupAlias pga = new PatchGroupAlias();
pga.setPatchGroupAlias(a1);
pga.addPatchedAlias(a2);
pga.addPatchedAlias(a3);
pga.addPatchedAlias(a4);
Set<BroadcastChannel> bc = pga.getBroadcastChannels();
mLog.debug("Channels:" + bc);
}
}

View File

@@ -18,6 +18,8 @@
******************************************************************************/
package alias.id;
import org.apache.commons.lang3.Validate;
import java.util.regex.Pattern;
public class WildcardID implements Comparable<WildcardID>
@@ -45,7 +47,7 @@ public class WildcardID implements Comparable<WildcardID>
*/
public WildcardID(String value)
{
assert(value != null && value.contains(WILDCARD) && value.length() < 10);
Validate.isTrue(value != null && value.contains(WILDCARD) && value.length() < 10);
mValue = value;

View File

@@ -25,76 +25,99 @@ import javax.xml.bind.annotation.XmlAttribute;
public class BroadcastChannel extends AliasID implements Comparable<BroadcastChannel>
{
private String mChannelName;
private String mChannelName;
public BroadcastChannel()
{
//JAXB Constructor
}
public BroadcastChannel()
{
//JAXB Constructor
}
@Override
public int compareTo(BroadcastChannel other)
{
if(mChannelName != null && other.getChannelName() != null)
{
return mChannelName.compareTo(other.getChannelName());
}
else if(mChannelName != null)
{
return -1;
}
else
{
return 1;
}
}
@Override
public int compareTo(BroadcastChannel other)
{
if(mChannelName != null && other.getChannelName() != null)
{
return mChannelName.compareTo(other.getChannelName());
}
else if(mChannelName != null)
{
return -1;
}
else
{
return 1;
}
}
/**
* Creates a named broadcast channel
*/
public BroadcastChannel(String channelName)
{
mChannelName = channelName;
}
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof BroadcastChannel))
{
return false;
}
/**
* Name of the broadcastAudio channel configuration
*/
@XmlAttribute(name="channel")
public String getChannelName()
{
return mChannelName;
}
BroadcastChannel that = (BroadcastChannel)o;
/**
* Sets the name of the broadcastAudio channel configuration
*/
public void setChannelName(String channel)
{
mChannelName = channel;
}
return getChannelName() != null ? getChannelName().equals(that.getChannelName()) : that.getChannelName() == null;
}
@Override
public AliasIDType getType()
{
return AliasIDType.BROADCAST_CHANNEL;
}
@Override
public int hashCode()
{
return getChannelName() != null ? getChannelName().hashCode() : 0;
}
@Override
public boolean isValid()
{
return mChannelName != null;
}
/**
* Creates a named broadcast channel
*/
public BroadcastChannel(String channelName)
{
mChannelName = channelName;
}
@Override
public boolean matches( AliasID id )
{
return false;
}
@Override
public String toString()
{
/**
* Name of the broadcastAudio channel configuration
*/
@XmlAttribute(name = "channel")
public String getChannelName()
{
return mChannelName;
}
/**
* Sets the name of the broadcastAudio channel configuration
*/
public void setChannelName(String channel)
{
mChannelName = channel;
}
@Override
public AliasIDType getType()
{
return AliasIDType.BROADCAST_CHANNEL;
}
@Override
public boolean isValid()
{
return mChannelName != null;
}
@Override
public boolean matches(AliasID id)
{
return false;
}
@Override
public String toString()
{
if(isValid())
{
return "Broadcast Channel: " + mChannelName;
@@ -103,5 +126,5 @@ public class BroadcastChannel extends AliasID implements Comparable<BroadcastCha
{
return "Broadcast Channel: None Selected";
}
}
}
}

View File

@@ -90,7 +90,8 @@ public class AudioChannelPanel extends JPanel
private void init()
{
setLayout(new MigLayout("align center center, insets 0 0 0 0", "[][][align right]0[grow,fill]", ""));
setLayout(new MigLayout("align center center, insets 0 0 0 0",
"[][][align right]0[grow,fill]", ""));
setBackground(mBackgroundColor);
mMutedLabel.setFont(mFont);
@@ -109,7 +110,7 @@ public class AudioChannelPanel extends JPanel
mTo.setFont(mFont);
mTo.setForeground(mValueColor);
add(mTo);
add(mTo,"wmin 10lp");
}
@Override
@@ -159,30 +160,40 @@ public class AudioChannelPanel extends JPanel
* does not occur on the Swing event thread -- wrap any calls to this
* method with an event thread call.
*/
private void updateLabel(JLabel valueLabel, String value, JLabel aliasLabel, Alias alias)
private void updateLabel(JLabel textLabel, String value, JLabel iconLabel, Alias alias)
{
if(value != null)
{
if(alias != null)
{
valueLabel.setText(alias.getName());
aliasLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18));
iconLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18));
String aliasName = alias.getName();
//Truncate label if length exceeds 33 characters - hack!
if(aliasName.length() > 33)
{
textLabel.setText(aliasName.substring(0,30) + "...");
}
else
{
textLabel.setText(aliasName);
}
}
else
{
valueLabel.setText(value);
aliasLabel.setIcon(null);
iconLabel.setIcon(null);
textLabel.setText(value);
}
}
else
{
valueLabel.setText("-----");
aliasLabel.setIcon(null);
iconLabel.setIcon(null);
textLabel.setText("-----");
}
}
/**
* Processes audio metadata to update this panel's display values
*/

View File

@@ -33,8 +33,7 @@ public class AudioChannelsPanel extends JPanel
public AudioChannelsPanel(IconManager iconManager, SettingsManager settingsManager, IAudioController controller)
{
// setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill][][grow,fill]", "[grow,fill]"));
setLayout(new MigLayout("insets 0 0 0 0", "[][sg,grow,fill][][sg,grow,fill]", "[grow,fill]"));
setLayout(new MigLayout("insets 0 0 0 0", "[][sg abc,grow,fill][][sg abc,grow,fill]", "[grow,fill]"));
setBackground(Color.BLACK);
@@ -44,7 +43,7 @@ public class AudioChannelsPanel extends JPanel
for(int x = 0; x < outputs.size(); x++)
{
add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x)), "growx");
add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x)));
if(x < outputs.size() - 1)
{

View File

@@ -86,6 +86,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
loadSettings();
}
/**
* Loads the saved mixer configuration or a default configuration for audio playback.
*/
private void loadSettings()
{
MixerChannelConfiguration configuration = null;
@@ -132,6 +135,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
}
}
/**
* Creates a default audio playback configuration with a mono audio playback channel.
*/
private MixerChannelConfiguration getDefaultConfiguration()
{
/* Use the system default mixer and mono channel as default startup */
@@ -161,6 +167,9 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mAudioOutputConnections.clear();
}
/**
* Primary ingest point for audio produced by all decoding channels, for distribution to audio playback devices.
*/
@Override
public synchronized void receive(AudioPacket packet)
{
@@ -176,11 +185,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
for(AudioOutputConnection connection : mAudioOutputConnections)
{
if(connection.isInactive() &&
mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID()))
{
mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.disconnect();
mAvailableConnectionCount++;
changed = true;
}
}
@@ -192,7 +201,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
}
/**
* Identifies the lowest priority channel connection
* Identifies the lowest priority channel connection where the a higher value indicates a lower priority.
*/
private void updateLowestPriorityAssignment()
{
@@ -201,19 +210,32 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
for(AudioOutputConnection connection : mAudioOutputConnections)
{
if(connection.isConnected() &&
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() > connection.getPriority()))
(mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() < connection.getPriority()))
{
mLowestPriorityConnection = connection;
}
}
}
/**
* Configures audio playback to use the configuration specified in the entry argument.
*
* @param entry to use in configuring the audio playback setup.
* @throws AudioException if there is an error
*/
@Override
public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException
{
setMixerChannelConfiguration(entry, true);
}
/**
* Configures audio playback to use the configuration specified in the entry argument.
*
* @param entry to use in configuring the audio playback setup.
* @param saveSettings to save the audio playback configuration settings in the properties file.
* @throws AudioException if there is an error
*/
public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException
{
if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO))
@@ -266,9 +288,8 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
}
/**
* Clears all channel assignments and terminates all audio outputs in
* preparation for complete shutdown or change to another mixer/channel
* configuration
* Clears all channel assignments and terminates all audio outputs in preparation for complete shutdown or change
* to another mixer/channel configuration
*/
private void disposeCurrentConfiguration()
{
@@ -288,12 +309,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
mLowestPriorityConnection = null;
}
/**
* Current audio playback mixer channel configuration setting.
*/
@Override
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
{
return mMixerChannelConfiguration;
}
/**
* List of audio outputs available for the current mixer channel configuration
*/
@Override
public List<AudioOutput> getAudioOutputs()
{
@@ -311,12 +338,18 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
return outputs;
}
/**
* Adds an audio event listener to receive audio event notifications.
*/
@Override
public void addControllerListener(Listener<AudioEvent> listener)
{
mControllerBroadcaster.addListener(listener);
}
/**
* Removes an audio event listener from receiving audio event notifications.
*/
@Override
public void removeControllerListener(Listener<AudioEvent> listener)
{
@@ -324,16 +357,15 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
}
/**
* Returns an audio output connection for the packet if one is
* available, or overrides an existing lower priority connection.
* Returns null if no connection is available for the audio packet.
* Returns an audio output connection for the packet if one is available, or overrides an existing lower priority
* connection. Returns null if no connection is available for the audio packet.
*
* @param packet from a source
* @param audioPacket from a decoding channel source
* @return an audio output connection or null
*/
private AudioOutputConnection getConnection(AudioPacket packet)
private AudioOutputConnection getConnection(AudioPacket audioPacket)
{
int channelMetadataID = packet.getMetadata().getMetadataID();
int channelMetadataID = audioPacket.getMetadata().getMetadataID();
//Use an existing connection
if(mChannelConnectionMap.containsKey(channelMetadataID))
@@ -341,37 +373,35 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
return mChannelConnectionMap.get(channelMetadataID);
}
//Connect to and use an available connection
//Connect to an unused, available connection
if(mAvailableConnectionCount > 0)
{
for(AudioOutputConnection connection : mAudioOutputConnections)
{
if(connection.isDisconnected())
{
connection.connect(channelMetadataID);
connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority());
mChannelConnectionMap.put(channelMetadataID, connection);
mAvailableConnectionCount--;
return connection;
}
}
}
//Preempt an existing connection and connect it to this higher priority source
else //Check for a channel priority override
//Preempt an existing lower priority connection and connect when this is a higher priority packet
else
{
int priority = packet.getMetadata().getAudioPriority();
int priority = audioPacket.getMetadata().getAudioPriority();
if(mLowestPriorityConnection != null &&
priority < mLowestPriorityConnection.getPriority())
AudioOutputConnection connection = mLowestPriorityConnection;
if(connection != null && priority < connection.getPriority())
{
AudioOutputConnection connection = mLowestPriorityConnection;
mChannelConnectionMap.remove(connection.getChannelMetadataID());
connection.connect(channelMetadataID);
connection.connect(channelMetadataID, priority);
mChannelConnectionMap.put(channelMetadataID, connection);
updateLowestPriorityAssignment();
return connection;
}
}
@@ -396,7 +426,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
for(AudioPacket packet : packets)
{
/* Don't process any packet's marked as do not monitor */
/* Don't process any packet's marked as do not monitor */
if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO)
{
AudioOutputConnection connection = getConnection(packet);
@@ -494,9 +524,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
* Connects this assignment to the indicated source so that audio
* packets from this source can be sent to the audio output
*/
public void connect(int source)
public void connect(int source, int priority)
{
mChannelMetadataID = source;
mPriority = priority;
updateLowestPriorityAssignment();
mAudioOutput.updateTimestamp();
}

View File

@@ -383,6 +383,7 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
{
setIcon(UNMUTED_ICON);
setBorderPainted(false);
getAccessibleContext().setAccessibleName("Mute");
addActionListener(new ActionListener()
{
@@ -402,6 +403,7 @@ public class AudioPanel extends JPanel implements Listener<AudioEvent>
public void run()
{
setIcon(mMuted ? MUTED_ICON : UNMUTED_ICON);
getAccessibleContext().setAccessibleName(mMuted ? "Unmute" : "Mute");
}
});
}

View File

@@ -68,7 +68,7 @@ public enum BroadcastState
/**
* Specified mount point is already in use
*/
MOUNT_POINT_IN_USE("Mount Point In Use", true),
MOUNT_POINT_IN_USE("Mount Point In Use", false),
/**
* Server is not known or reachable

View File

@@ -145,6 +145,10 @@ public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdate
session.write(updateRequest);
}
}
catch(UnresolvedAddressException uae)
{
//Do nothing - the server is temporarily unavailable
}
catch(Exception e)
{
Throwable throwableCause = e.getCause();

View File

@@ -156,6 +156,8 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
disconnect();
}
mConnecting.set(false);
}
};
@@ -176,8 +178,6 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
{
mStreamingSession.closeNow();
}
mConnecting.set(false);
}
/**
@@ -256,6 +256,7 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
mSocketConnector = null;
mConnecting.set(false);
super.sessionClosed(session);
}

View File

@@ -25,6 +25,7 @@ import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.write.WriteTimeoutException;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
@@ -35,6 +36,7 @@ import util.ThreadPool;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.nio.channels.UnresolvedAddressException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -43,7 +45,8 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
private final static String TERMINATOR = "\r\n";
private final static String SEPARATOR = ":";
private static final long RECONNECT_INTERVAL_MILLISECONDS = 30000; //30 seconds
private static final long RECONNECT_INTERVAL_MILLISECONDS = 3000; //3 seconds
private static final long CONNECTION_ATTEMPT_TIMEOUT = 5000; //5 seconds
private NioSocketConnector mSocketConnector;
private IoSession mStreamingSession = null;
@@ -98,10 +101,11 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
if(mSocketConnector == null)
{
mSocketConnector = new NioSocketConnector();
mSocketConnector.setConnectTimeoutCheckInterval(10000);
mSocketConnector.setConnectTimeoutCheckInterval(CONNECTION_ATTEMPT_TIMEOUT);
// mSocketConnector.getFilterChain().addLast("logger",
// new LoggingFilter(IcecastTCPAudioBroadcaster.class));
// LoggingFilter loggingFilter = new LoggingFilter(IcecastTCPAudioBroadcaster.class);
// loggingFilter.setMessageSentLogLevel(LogLevel.NONE);
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
mSocketConnector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new IcecastCodecFactory()));
@@ -122,9 +126,20 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
ConnectFuture future = mSocketConnector
.connect(new InetSocketAddress(getBroadcastConfiguration().getHost(),
getBroadcastConfiguration().getPort()));
future.awaitUninterruptibly();
boolean connected = future.await(CONNECTION_ATTEMPT_TIMEOUT, TimeUnit.MILLISECONDS);
mStreamingSession = future.getSession();
}
catch(UnresolvedAddressException uae)
{
//What do you do with this? Nothing.
}
catch(InterruptedException ie)
{
//We couldn't connect within the timeout period -- disconnect so that we can try again later
disconnect();
}
catch(RuntimeIoException rie)
{
Throwable throwableCause = rie.getCause();
@@ -136,16 +151,22 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
else if(throwableCause != null)
{
setBroadcastState(BroadcastState.ERROR);
mLog.debug("Failed to connect", rie);
mLog.debug("[" + getStreamName() + "] failed to connect", rie);
}
else
{
setBroadcastState(BroadcastState.ERROR);
mLog.debug("Failed to connect - no exception is available");
mLog.debug("[" + getStreamName() + "] failed to connect - no exception is available");
}
disconnect();
}
catch(Throwable t)
{
disconnect();
}
mConnecting.set(false);
}
};
@@ -161,12 +182,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
*/
public void disconnect()
{
if(connected())
if(connected() && mStreamingSession != null)
{
if(mStreamingSession != null)
{
mStreamingSession.closeNow();
}
mStreamingSession.closeNow();
}
else
{
mLastConnectionAttempt = System.currentTimeMillis();
}
}
@@ -223,17 +245,18 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
public void sessionClosed(IoSession session) throws Exception
{
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.DISCONNECTED);
}
mSocketConnector.dispose();
mStreamingSession = null;
mSocketConnector = null;
mConnecting.set(false);
super.sessionClosed(session);
}
@@ -248,40 +271,41 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
String reason = ioe.getMessage();
if(reason.startsWith("Connection reset"))
if(connected())
{
disconnect();
}
else if(reason.startsWith("Connection reset"))
{
mLog.info("Streaming connection reset by remote server - reestablishing connection");
disconnect();
connect();
}
else if(reason.startsWith("Operation timed out"))
{
mLog.info("Streaming connection timed out - resetting connection");
disconnect();
connect();
}
else
{
setBroadcastState(BroadcastState.ERROR);
disconnect();
mLog.error("Unrecognized IO error: " + reason + ". Streaming halted.");
}
}
else if(ioe instanceof WriteTimeoutException)
{
disconnect();
}
else
{
setBroadcastState(BroadcastState.ERROR);
disconnect();
mLog.error("Unspecified IO error - streaming halted.");
mLog.error("[" + getStreamName() + "] Unspecified IO error - streaming halted.", cause);
}
}
else
{
mLog.error("Broadcast error", cause);
mLog.error("[" + getStreamName() + "] Broadcast error", cause);
setBroadcastState(BroadcastState.ERROR);
disconnect();
}
mConnecting.set(false);
}
@Override
@@ -301,11 +325,19 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
{
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
}
else if(message.contains("Content-Type: text/html"))
{
//Disregard ... this normally follows the mount point in use error
}
else if(message.contains("Invalid Password") ||
message.contains("Authentication Required"))
{
setBroadcastState(BroadcastState.INVALID_CREDENTIALS);
}
else if(message.contains("HTTP/1.1 501"))
{
disconnect(); //So that we can reconnect later
}
else
{
mLog.error("Unrecognized server response:" + message);
@@ -315,7 +347,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
}
else
{
mLog.error("Icecast TCP broadcaster - unrecognized message [ " + object.getClass() +
mLog.error("[" + getStreamName() + "]Icecast TCP broadcaster - unrecognized message [ " + object.getClass() +
"] received:" + object.toString());
}
}

View File

@@ -168,6 +168,12 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
disconnect();
}
catch(Throwable t)
{
disconnect();
}
mConnecting.set(false);
}
};
@@ -183,12 +189,13 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
*/
public void disconnect()
{
if(connected())
if(connected() && mStreamingSession != null)
{
if(mStreamingSession != null)
{
mStreamingSession.closeNow();
}
mStreamingSession.closeNow();
}
else
{
mLastConnectionAttempt = System.currentTimeMillis();
}
}
@@ -224,15 +231,15 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
@Override
public void sessionClosed(IoSession session) throws Exception
{
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.DISCONNECTED);
}
mSocketConnector.dispose();
mStreamingSession = null;
mSocketConnector = null;
mConnecting.set(false);
super.sessionClosed(session);
@@ -253,13 +260,11 @@ public class ShoutcastV1AudioBroadcaster extends AudioBroadcaster
{
mLog.info("Streaming connection reset by remote server - reestablishing connection");
disconnect();
connect();
}
else if(reason.startsWith("Operation timed out"))
{
mLog.info("Streaming connection timed out - resetting connection");
disconnect();
connect();
}
else
{

View File

@@ -50,6 +50,7 @@ import properties.SystemProperties;
import util.ThreadPool;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
@@ -203,6 +204,12 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
disconnect();
}
catch(Throwable t)
{
disconnect();
}
mConnecting.set(false);
}
};
@@ -218,13 +225,13 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
*/
public void disconnect()
{
if(connected())
if(connected() && mStreamingSession != null)
{
if(mStreamingSession != null)
{
mStreamingSession.write(new TerminateBroadcast());
mStreamingSession.closeNow();
}
mStreamingSession.closeNow();
}
else
{
mLastConnectionAttempt = System.currentTimeMillis();
}
}
@@ -244,43 +251,50 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?><metadata>");
sb.append(UltravoxMetadata.ALBUM_TITLE.asXML(getConfiguration().getName()));
sb.append(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION.asXML(SystemProperties.getInstance().getApplicationName()));
if(getConfiguration().hasGenre())
try
{
sb.append(UltravoxMetadata.GENRE.asXML(getConfiguration().getGenre()));
}
sb.append(UltravoxMetadata.ALBUM_TITLE.asXML(getConfiguration().getName()));
if(getConfiguration().hasURL())
{
sb.append(UltravoxMetadata.URL.asXML(getConfiguration().getURL()));
}
sb.append(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION.asXML(SystemProperties.getInstance().getApplicationName()));
if(metadata != null)
{
if(metadata.getPrimaryAddressTo().hasAlias())
if(getConfiguration().hasGenre())
{
sb.append(UltravoxMetadata.TITLE_2.asXML(metadata.getPrimaryAddressTo().getAlias().getName()));
}
else if(metadata.getPrimaryAddressTo().hasIdentifier())
{
sb.append(UltravoxMetadata.TITLE_2.asXML(metadata.getPrimaryAddressTo().getIdentifier()));
sb.append(UltravoxMetadata.GENRE.asXML(getConfiguration().getGenre()));
}
if(metadata.getPrimaryAddressFrom().hasAlias())
if(getConfiguration().hasURL())
{
sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + metadata.getPrimaryAddressFrom().getAlias().getName()));
sb.append(UltravoxMetadata.URL.asXML(getConfiguration().getURL()));
}
else if(metadata.getPrimaryAddressFrom().hasIdentifier())
if(metadata != null)
{
sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + metadata.getPrimaryAddressFrom().getIdentifier()));
if(metadata.getPrimaryAddressTo().hasAlias())
{
sb.append(UltravoxMetadata.TITLE_2.asXML(metadata.getPrimaryAddressTo().getAlias().getName()));
}
else if(metadata.getPrimaryAddressTo().hasIdentifier())
{
sb.append(UltravoxMetadata.TITLE_2.asXML(metadata.getPrimaryAddressTo().getIdentifier()));
}
if(metadata.getPrimaryAddressFrom().hasAlias())
{
sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + metadata.getPrimaryAddressFrom().getAlias().getName()));
}
else if(metadata.getPrimaryAddressFrom().hasIdentifier())
{
sb.append(UltravoxMetadata.TITLE_3.asXML("From:" + metadata.getPrimaryAddressFrom().getIdentifier()));
}
}
else
{
sb.append(UltravoxMetadata.TITLE_2.asXML("Scanning ..."));
}
}
else
catch(UnsupportedEncodingException uee)
{
sb.append(UltravoxMetadata.TITLE_2.asXML("Scanning ..."));
mLog.error("UTF-8 Encoding is not supported - shoutcast/ultravox metadata will not be updated");
}
sb.append("</metadata>");
@@ -339,15 +353,15 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
@Override
public void sessionClosed(IoSession session) throws Exception
{
mLastConnectionAttempt = System.currentTimeMillis();
//If there is already an error state, don't override it. Otherwise, set state to disconnected
if(!getBroadcastState().isErrorState())
{
setBroadcastState(BroadcastState.DISCONNECTED);
}
mSocketConnector.dispose();
mStreamingSession = null;
mSocketConnector = null;
mConnecting.set(false);
super.sessionClosed(session);
@@ -368,13 +382,11 @@ public class ShoutcastV2AudioBroadcaster extends AudioBroadcaster implements IBr
{
mLog.info("Streaming connection reset by remote server - reestablishing connection");
disconnect();
connect();
}
else if(reason.startsWith("Operation timed out"))
{
mLog.info("Streaming connection timed out - resetting connection");
disconnect();
connect();
}
else
{

View File

@@ -21,6 +21,7 @@ package audio.broadcast.shoutcast.v2;
import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastServerType;
import org.apache.commons.lang3.Validate;
import javax.xml.bind.annotation.XmlAttribute;
@@ -90,7 +91,7 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
*/
public void setStreamID(int streamID)
{
assert(1 <= streamID && streamID <= Integer.MAX_VALUE);
Validate.isTrue(1 <= streamID && streamID <= Integer.MAX_VALUE);
mStreamID = streamID;
}

View File

@@ -18,6 +18,8 @@
******************************************************************************/
package audio.broadcast.shoutcast.v2.ultravox;
import org.apache.commons.lang3.Validate;
public class NegotiateMaxPayloadSize extends UltravoxMessage
{
/**
@@ -47,8 +49,8 @@ public class NegotiateMaxPayloadSize extends UltravoxMessage
*/
public void setMaximumPayloadSize(int desiredMaximum, int minimum)
{
assert(desiredMaximum <= 16377);
assert(minimum <= 16377);
Validate.isTrue(desiredMaximum <= 16377);
Validate.isTrue(minimum <= 16377);
StringBuilder sb = new StringBuilder();
sb.append(desiredMaximum).append(":").append(minimum);

View File

@@ -19,6 +19,7 @@
package audio.broadcast.shoutcast.v2.ultravox;
import bits.BinaryMessage;
import org.apache.commons.lang3.Validate;
import java.util.Arrays;
@@ -154,7 +155,7 @@ public abstract class UltravoxMessage
*/
public void setSendQueuePriority(int priority)
{
assert(0 <= priority && priority <= 7);
Validate.isTrue(0 <= priority && priority <= 7);
mMessage.setInt(priority, SEND_QUEUE_PRIORITY);
}

View File

@@ -18,6 +18,9 @@
******************************************************************************/
package audio.broadcast.shoutcast.v2.ultravox;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public enum UltravoxMetadata
{
ALBUM_TITLE("TALB"),
@@ -58,11 +61,11 @@ public enum UltravoxMetadata
* @param value to enclose in XML tags
* @return xml tag wrapped value
*/
public String asXML(String value)
public String asXML(String value) throws UnsupportedEncodingException
{
StringBuilder sb = new StringBuilder();
sb.append("<").append(getXMLTag()).append(">");
sb.append(value);
sb.append(new String(value.getBytes(), "UTF-8"));
sb.append("</").append(getXMLTag()).append(">");
return sb.toString();

View File

@@ -22,6 +22,7 @@ import java.util.BitSet;
import java.util.logging.LogManager;
import edac.CRC;
import org.apache.commons.lang3.Validate;
public class BinaryMessage extends BitSet
{
@@ -135,6 +136,17 @@ public class BinaryMessage extends BitSet
{
mCRC = crc;
}
/**
* Indicates the number of bit errors that were corrected in this message.
*
* @see CorrectedBinaryMessage subclass that supports setting this value
* @return number of corrected bits in this message
*/
public int getCorrectedBitCount()
{
return 0;
}
/**
* Current pointer index
@@ -456,7 +468,7 @@ public class BinaryMessage extends BitSet
*/
public byte getByte(int index)
{
assert((index + 7) <= size());
Validate.isTrue((index + 7) <= size());
int value = 0;
@@ -481,7 +493,7 @@ public class BinaryMessage extends BitSet
*/
public void setByte(int index, byte value)
{
assert((index + 8) <= size());
Validate.isTrue((index + 8) <= size());
int mask = 0x80;

View File

@@ -0,0 +1,65 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 bits;
import java.util.BitSet;
public class CorrectedBinaryMessage extends BinaryMessage
{
private int mCorrectedBitCount;
/**
* Subclass of binary message class to allow capturing a corrected bits metric.
* @param size of message to create.
*/
public CorrectedBinaryMessage(int size)
{
super(size);
}
public CorrectedBinaryMessage(int size, boolean[] bitsToPreload)
{
super(size, bitsToPreload);
}
public CorrectedBinaryMessage(BitSet bitset, int size)
{
super(bitset, size);
}
public CorrectedBinaryMessage(byte[] data)
{
super(data);
}
@Override
public int getCorrectedBitCount()
{
return mCorrectedBitCount;
}
/**
* Sets the number of bits that were corrected in this message
* @param count of corrected bits
*/
public void setCorrectedBitCount(int count)
{
mCorrectedBitCount = count;
}
}

View File

@@ -17,6 +17,8 @@
******************************************************************************/
package bits;
import org.apache.commons.lang3.Validate;
import java.util.ArrayList;
import java.util.List;
@@ -39,7 +41,7 @@ public class MultiSyncPatternMatcher
public MultiSyncPatternMatcher( int syncSize )
{
assert( syncSize < 64 );
Validate.isTrue(syncSize < 64);
//Setup a bit mask of all ones the length of the sync pattern
mMask = (long)( ( Math.pow( 2, syncSize ) ) - 1 );

View File

@@ -38,7 +38,7 @@ public class ChannelDetailPanel extends JPanel implements Listener<ProcessingCha
private JLabel mSystemLabel;
private JLabel mSiteLabel;
private JLabel mNameLabel;
private JTextArea mDetailTextArea;
private JTextArea mDetailTextPane;
private ChannelProcessingManager mChannelProcessingManager;
private ProcessingChain mProcessingChain;
@@ -82,10 +82,10 @@ public class ChannelDetailPanel extends JPanel implements Listener<ProcessingCha
add(buttonPanel, "wrap");
mDetailTextArea = new JTextArea(EMPTY_DETAILS);
DefaultCaret caret = (DefaultCaret)mDetailTextArea.getCaret();
mDetailTextPane = new JTextArea(EMPTY_DETAILS);
DefaultCaret caret = (DefaultCaret)mDetailTextPane.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
add(new JScrollPane(mDetailTextArea));
add(new JScrollPane(mDetailTextPane));
}
@Override
@@ -126,7 +126,7 @@ public class ChannelDetailPanel extends JPanel implements Listener<ProcessingCha
mSystemLabel.setText(system);
mSiteLabel.setText(site);
mNameLabel.setText(name);
mDetailTextArea.setText(details);
mDetailTextPane.setText(details);
}
});
}

View File

@@ -0,0 +1,31 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 channel.heartbeat;
public class Heartbeat
{
/**
* Periodic pulse that is broadcast to subscribing modules within the demodulating chain so that
* monitoring of state and other attributes can occur on the primary demodulation and decoding
* thread.
*/
public Heartbeat()
{
}
}

View File

@@ -0,0 +1,26 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 channel.heartbeat;
import sample.Listener;
public interface IHeartbeatListener
{
public Listener<Heartbeat> getHeartbeatListener();
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 channel.heartbeat;
import sample.Listener;
public interface IHeartbeatProvider
{
public void setHeartbeatListener(Listener<Heartbeat> listener);
public void removeHeartbeatListener();
}

View File

@@ -19,8 +19,6 @@
package channel.metadata;
import alias.Alias;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import java.util.ArrayList;
@@ -30,8 +28,6 @@ import java.util.Map;
public class AttributeMonitor<T>
{
private final static Logger mLog = LoggerFactory.getLogger(AttributeMonitor.class);
private Attribute mAttribute;
private Listener<AttributeChangeRequest> mListener;
private boolean mHeuristicsEnabled = true;
@@ -50,8 +46,8 @@ public class AttributeMonitor<T>
* an internal mapping of values to occurrence counts is updated and a change is emitted once a value that is
* different from the current value, has a higher occurrence count than the current value. If heuristics are
* disabled, then a change is emitted at each change in the value.
* @param t
*
* @param t type
* @return true if the value was changed after processing
*/
public boolean process(T t)
@@ -83,7 +79,7 @@ public class AttributeMonitor<T>
{
int count = mOccurrenceCounts.get(mCurrentValue);
for(Map.Entry<T,Integer> entry: mOccurrenceCounts.entrySet())
for(Map.Entry<T,Integer> entry : mOccurrenceCounts.entrySet())
{
if(entry.getValue() > count)
{
@@ -155,35 +151,4 @@ public class AttributeMonitor<T>
{
return mCurrentValue != null;
}
public static void main(String[] args)
{
AttributeMonitor<String> monitor = new AttributeMonitor<>(Attribute.PRIMARY_ADDRESS_TO, new Listener<AttributeChangeRequest>()
{
@Override
public void receive(AttributeChangeRequest attributeChangeRequest)
{
mLog.debug("Attribute Changed - value:" + attributeChangeRequest.getValue());
}
});
monitor.addIllegalValue("0000");
monitor.addIllegalValue("000000");
monitor.process("ABCD");
monitor.process("EFGH");
monitor.process("0000");
monitor.process("0000");
monitor.process("ABCD");
monitor.reset();
monitor.process("EFGH");
monitor.process("ABCD");
monitor.process("ABCD");
monitor.process("EFGH");
monitor.process("EFGH");
monitor.process("000000");
monitor.process("000000");
monitor.process("000000");
monitor.process("000000");
}
}

View File

@@ -32,7 +32,7 @@ import java.util.Map;
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MutableMetadataChangeEvent>
{
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.0000" );
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" );
public static final int COLUMN_STATE = 0;
public static final int COLUMN_DECODER = 1;

View File

@@ -26,6 +26,7 @@ import controller.channel.ChannelUtils;
import icon.IconManager;
import module.ProcessingChain;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
@@ -140,7 +141,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
public AliasedValueCellRenderer(Attribute attribute)
{
assert(attribute != null);
Validate.isTrue(attribute != null);
mAttribute = attribute;
setHorizontalAlignment(JLabel.CENTER);
}
@@ -210,6 +211,10 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "idle", Color.WHITE);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "idle", Color.BLUE);
break;
case OVERFLOW:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "overflow", Color.RED);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "overflow", Color.YELLOW);
break;
case RESET:
background = SystemProperties.getInstance().get(PROPERTY_PREFIX_BACKGROUND + "reset", Color.PINK);
foreground = SystemProperties.getInstance().get(PROPERTY_PREFIX_FOREGROUND + "reset", Color.YELLOW);

View File

@@ -51,7 +51,7 @@ public class ChannelMetadataViewer extends JPanel
mChannelDetailPanel = new ChannelDetailPanel(channelProcessingManager);
mCallEventPanel = new CallEventPanel(iconManager);
mMessageActivityPanel = new MessageActivityPanel(channelProcessingManager);
mChannelSpectrumPanel = new ChannelSpectrumPanel(settingsManager, channelProcessingManager);
// mChannelSpectrumPanel = new ChannelSpectrumPanel(settingsManager, channelProcessingManager);
mChannelMetadataPanel = new ChannelMetadataPanel(channelProcessingManager, iconManager);
init();

View File

@@ -28,6 +28,8 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class Metadata
{
@@ -61,7 +63,7 @@ public class Metadata
private Integer mAudioPriority;
protected Boolean mRecordable = false;
protected Boolean mDoNotRecord;
private List<BroadcastChannel> mBroadcastChannels;
private Set<BroadcastChannel> mBroadcastChannels;
/**
@@ -166,7 +168,7 @@ public class Metadata
/**
* List of de-duplicated broadcast channels aggregated from the primary and secondary TO/FROM aliases.
*/
public List<BroadcastChannel> getBroadcastChannels()
public Set<BroadcastChannel> getBroadcastChannels()
{
if(mBroadcastChannels == null)
{
@@ -582,50 +584,26 @@ public class Metadata
*/
private void determineBroadcastChannels()
{
mBroadcastChannels = new ArrayList<>();
mBroadcastChannels = new TreeSet<>();
if(mPrimaryAddressTo.hasAlias() && mPrimaryAddressTo.getAlias().isStreamable())
{
for(BroadcastChannel broadcastChannel: mPrimaryAddressTo.getAlias().getBroadcastChannels())
{
if(!mBroadcastChannels.contains(broadcastChannel))
{
mBroadcastChannels.add(broadcastChannel);
}
}
mBroadcastChannels.addAll(mPrimaryAddressTo.getAlias().getBroadcastChannels());
}
if(mPrimaryAddressFrom.hasAlias() && mPrimaryAddressFrom.getAlias().isStreamable())
{
for(BroadcastChannel broadcastChannel: mPrimaryAddressFrom.getAlias().getBroadcastChannels())
{
if(!mBroadcastChannels.contains(broadcastChannel))
{
mBroadcastChannels.add(broadcastChannel);
}
}
mBroadcastChannels.addAll(mPrimaryAddressFrom.getAlias().getBroadcastChannels());
}
if(mSecondaryAddressTo.hasAlias() && mSecondaryAddressTo.getAlias().isStreamable())
{
for(BroadcastChannel broadcastChannel: mSecondaryAddressTo.getAlias().getBroadcastChannels())
{
if(!mBroadcastChannels.contains(broadcastChannel))
{
mBroadcastChannels.add(broadcastChannel);
}
}
mBroadcastChannels.addAll(mSecondaryAddressTo.getAlias().getBroadcastChannels());
}
if(mSecondaryAddressFrom.hasAlias() && mSecondaryAddressFrom.getAlias().isStreamable())
{
for(BroadcastChannel broadcastChannel: mSecondaryAddressFrom.getAlias().getBroadcastChannels())
{
if(!mBroadcastChannels.contains(broadcastChannel))
{
mBroadcastChannels.add(broadcastChannel);
}
}
mBroadcastChannels.addAll(mSecondaryAddressFrom.getAlias().getBroadcastChannels());
}
}

View File

@@ -20,6 +20,8 @@ package channel.state;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
import channel.heartbeat.Heartbeat;
import channel.heartbeat.IHeartbeatListener;
import channel.metadata.Attribute;
import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestListener;
@@ -35,14 +37,17 @@ import module.decode.event.ICallEventProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.IOverflowListener;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, ISquelchStateProvider, IAttributeChangeRequestListener
IDecoderStateEventProvider, IFrequencyChangeListener, IHeartbeatListener, ISquelchStateProvider,
IAttributeChangeRequestListener, IOverflowListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
@@ -55,14 +60,15 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
private Listener<DecoderStateEvent> mDecoderStateListener;
private Listener<SquelchState> mSquelchStateListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
private StateMonitor mStateMonitor = new StateMonitor();
private ScheduledFuture<?> mStateMonitorFuture;
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent;
private FrequencyChangeListener mFrequencyChangeListener;
private boolean mSquelchLocked = false;
private boolean mSelected = false;
private boolean mSourceOverflow = false;
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
private long mTrafficChannelFadeTimeout = DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
@@ -71,30 +77,21 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
/**
* Channel state tracks the overall state of all processing modules and decoders
* configured for the channel and provides squelch control and decoder state
* reset events.
* Channel state tracks the overall state of all processing modules and decoders configured for the channel and
* provides squelch control and decoder state reset events.
*
* Uses a state enumeration that defines allowable channel state transitions in
* order to track a call or data decode event from start to finish. Uses a
* timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details
* about the call, before the state is reset to IDLE.
*
* Since this class is multi-threaded between decoder events and the internal
* timer that monitors for inactivity, this class uses the mState object for
* thread synchronization.
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
* reset to IDLE.
*
* State Descriptions:
* IDLE Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL
* Decoding states.
* FADE The phase after a voice or data call when either an explicit
* call end has been received, or when no new signalling updates
* have been received, and the fade timer has expired. This phase
* allows for gui updates to signal to the user that the call is
* ended, while continuing to display the call details for the user
* TEARDOWN Indicates a traffic channel that will be torn down for reuse.
* IDLE: Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
* signal to the user that the call is ended, while continuing to display the call details for the user
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
*/
public ChannelState(ChannelType channelType)
{
@@ -123,28 +120,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mState = State.IDLE;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
mSourceOverflow = false;
}
@Override
public void start(ScheduledExecutorService executor)
{
if(mStateMonitorFuture == null && mStateMonitor != null)
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
{
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
{
setState(State.CALL);
}
try
{
mStateMonitorFuture = executor.scheduleAtFixedRate(mStateMonitor, 0, 20, TimeUnit.MILLISECONDS);
}
catch(RejectedExecutionException ree)
{
mLog.error("state monitor scheduled task rejected", ree);
}
setState(State.CALL);
}
}
@@ -153,16 +139,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{
mMutableMetadata.resetAllAttributes();
if(mStateMonitorFuture != null)
{
boolean success = mStateMonitorFuture.cancel(true);
if(!success)
{
mLog.error("Couldn't stop monitoring scheduled future");
}
}
mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null)
@@ -173,8 +149,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mTrafficChannelCallEvent = null;
mStateMonitorFuture = null;
mSquelchLocked = false;
}
@@ -183,7 +157,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mCallEventListener = null;
mDecoderStateListener = null;
mSquelchStateListener = null;
mStateMonitor = null;
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
if(mFrequencyChangeListener == null)
{
mFrequencyChangeListener = new FrequencyChangeListener();
}
return mFrequencyChangeListener;
}
private boolean isStandardChannel()
@@ -292,73 +276,88 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
/**
* Sets the channel state to the specified state, or updates the timeout
* values so that the state monitor will not change state. Broadcasts a
* squelch event when the state changes and the audio squelch state should
* change. Also broadcasts changed attribute and decoder state events so
* that external processes can maintain sync with this channel state.
* Sets the channel state to the specified state, or updates the timeout values so that the state monitor will not
* change state. Broadcasts a squelch event when the state changes and the audio squelch state should change. Also
* broadcasts changed attribute and decoder state events so that external processes can maintain sync with this
* channel state.
*/
protected void setState(State state)
{
synchronized(mState)
if(state == mState)
{
if(state == mState)
if(State.CALL_STATES.contains(state))
{
if(State.CALL_STATES.contains(state))
{
updateFadeTimeout();
}
updateFadeTimeout();
}
else if(state != mState && mState.canChangeTo(state))
}
else if(mState.canChangeTo(state))
{
switch(state)
{
switch(state)
{
case CONTROL:
//Don't allow traffic channels to be control channels,
//otherwise they can't transition to call tear down
if(isStandardChannel())
{
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
}
break;
case DATA:
case ENCRYPTED:
case CONTROL:
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
if(isStandardChannel())
{
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
break;
case CALL:
broadcast(SquelchState.UNSQUELCH);
updateFadeTimeout();
mState = state;
break;
case FADE:
processFadeState();
break;
case IDLE:
processIdleState();
break;
case TEARDOWN:
processTeardownState();
break;
case RESET:
mState = State.IDLE;
break;
default:
break;
}
}
break;
case DATA:
case ENCRYPTED:
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
break;
case CALL:
broadcast(SquelchState.UNSQUELCH);
updateFadeTimeout();
mState = state;
break;
case FADE:
processFadeState();
break;
case IDLE:
processIdleState();
break;
case TEARDOWN:
processTeardownState();
break;
case RESET:
mState = State.IDLE;
break;
default:
break;
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
}
else
{
// mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
}
}
/**
* Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages.
*/
@Override
public Listener<Heartbeat> getHeartbeatListener()
{
return mHeartbeatReceiver;
}
/**
* This method is invoked if the source buffer provider goes into overflow state. Since this is an external state,
* we use the mSourceOverflow variable to override the internal state reported to external listeners.
* @param overflow true to indicate an overflow state
*/
@Override
public void sourceOverflow(boolean overflow)
{
mSourceOverflow = overflow;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
}
/**
* Sets the state and processes related actions
*/
@@ -518,6 +517,23 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
broadcast(mTrafficChannelCallEvent);
}
/**
* Listener to receive frequency change events and rebroadcast them to the decoder states
*/
public class FrequencyChangeListener implements Listener<FrequencyChangeEvent>
{
@Override
public void receive(FrequencyChangeEvent frequencyChangeEvent)
{
if(frequencyChangeEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE)
{
long frequency = frequencyChangeEvent.getValue().longValue();
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
}
}
}
/**
* DecoderStateEvent receiver wrapper
*/
@@ -577,45 +593,41 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
/**
* Monitors decoder state events to automatically transition the channel
* state to IDLE (standard channel) or to TEARDOWN (traffic channel) when
* decoding stops or the monitored channel returns to a no signal state.
* Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
* functions.
*
* Provides a FADE transition state to allow for momentary decoding dropouts
* and to allow the user access to call details for a fade period upon
* call end.
* Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to
* TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state.
*
* Provides a FADE transition state to allow for momentary decoding dropouts and to allow the user access to call
* details for a fade period upon call end.
*/
public class StateMonitor implements Runnable
public class HeartbeatReceiver implements Listener<Heartbeat>
{
@Override
public void run()
public void receive(Heartbeat heartbeat)
{
try
{
synchronized(mState)
if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
{
if(State.CALL_STATES.contains(mState) &&
mFadeTimeout <= System.currentTimeMillis())
processFadeState();
}
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
{
if(isTrafficChannel())
{
processFadeState();
processTeardownState();
}
else if(mState == State.FADE &&
mEndTimeout <= System.currentTimeMillis())
else
{
if(isTrafficChannel())
{
processTeardownState();
}
else
{
processIdleState();
}
processIdleState();
}
}
}
catch(Exception e)
catch(Throwable e)
{
mLog.error("Exception thrown while state monitor is running " +
mLog.error("An error occurred while state monitor was running " +
"- state [" + getState() +
"] current [" + System.currentTimeMillis() +
"] mResetTimeout [" + mEndTimeout +

View File

@@ -23,6 +23,9 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
IAttributeChangeRequestProvider, ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, IMessageListener
{
protected String DIVIDER1 = "======================================================\n";
protected String DIVIDER2 = "------------------------------------------------------\n";
/* This has to be a broadcaster in order for references to persist */
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
private Broadcaster<AttributeChangeRequest> mAttributeChangeRequestBroadcaster = new Broadcaster<>();
@@ -51,6 +54,14 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
return mAliasList != null;
}
/**
* Provides subclass reference to the call event broadcaster
*/
protected Broadcaster<CallEvent> getCallEventBroadcaster()
{
return mCallEventBroadcaster;
}
/**
* Reset the decoder state to prepare for processing a different sample
* source

View File

@@ -1,17 +1,17 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
*
* This 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/>
******************************************************************************/
@@ -23,98 +23,94 @@ import java.util.EnumSet;
* Details the set of states for a channel and the allowable transition states
*/
public enum State
{
IDLE( "IDLE" )
{
@Override
public boolean canChangeTo( State state )
{
IDLE("IDLE")
{
return state != TEARDOWN &&
state != RESET;
}
},
CALL( "CALL" )
{
@Override
public boolean canChangeTo( State state )
@Override
public boolean canChangeTo(State state)
{
return state != TEARDOWN && state != RESET && state != OVERFLOW;
}
},
CALL("CALL")
{
return state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
DATA( "DATA" )
{
@Override
public boolean canChangeTo( State state )
@Override
public boolean canChangeTo(State state)
{
return state == CONTROL || state == DATA || state == ENCRYPTED || state == FADE || state == TEARDOWN;
}
},
DATA("DATA")
{
return state == CALL ||
state == CONTROL ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
ENCRYPTED( "ENCRYPTED" )
@Override
public boolean canChangeTo(State state)
{
return state == CALL || state == CONTROL || state == ENCRYPTED || state == FADE || state == TEARDOWN;
}
},
ENCRYPTED("ENCRYPTED")
{
@Override
public boolean canChangeTo(State state)
{
return state == FADE || state == TEARDOWN;
}
},
CONTROL("CONTROL")
{
@Override
public boolean canChangeTo(State state)
{
return state == IDLE || state == FADE;
}
},
FADE("FADE")
{
@Override
public boolean canChangeTo(State state)
{
return state != FADE && state != RESET && state != OVERFLOW;
}
},
OVERFLOW("OVERFLOW")
{
@Override
public boolean canChangeTo(State state)
{
return false;
}
},
RESET("RESET")
{
@Override
public boolean canChangeTo(State state)
{
return state == IDLE;
}
},
TEARDOWN("TEARDOWN")
{
@Override
public boolean canChangeTo(State state)
{
return state == RESET;
}
};
private String mDisplayValue;
public static final EnumSet<State> CALL_STATES = EnumSet.of(CALL, CONTROL, DATA, ENCRYPTED);
public static final EnumSet<State> IDLE_STATES = EnumSet.of(IDLE, FADE);
private State(String displayValue)
{
@Override
public boolean canChangeTo( State state )
{
return state == FADE || state == TEARDOWN;
}
},
CONTROL( "CONTROL" )
mDisplayValue = displayValue;
}
public abstract boolean canChangeTo(State state);
public String getDisplayValue()
{
@Override
public boolean canChangeTo( State state )
{
return state == IDLE ||
state == FADE;
}
},
FADE( "FADE" )
{
@Override
public boolean canChangeTo( State state )
{
return state != FADE &&
state != RESET;
}
},
RESET( "RESET" )
{
@Override
public boolean canChangeTo(State state)
{
return state == IDLE;
}
},
TEARDOWN( "TEARDOWN" )
{
@Override
public boolean canChangeTo(State state)
{
return state == RESET;
}
};
private String mDisplayValue;
public static final EnumSet<State> CALL_STATES = EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED );
public static final EnumSet<State> IDLE_STATES = EnumSet.of( IDLE, FADE );
private State( String displayValue )
{
mDisplayValue = displayValue;
}
public abstract boolean canChangeTo( State state );
public String getDisplayValue()
{
return mDisplayValue;
}
return mDisplayValue;
}
}

View File

@@ -63,13 +63,11 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
private Listener<CallEvent> mCallEventListener;
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private DecodeConfiguration mDecodeConfiguration;
private RecordConfiguration mRecordConfiguration;
private String mSystem;
private String mSite;
private String mAliasListName;
private CallEvent mPreviousDoNotMonitorCallEvent;
/**
* Monitors call events and allocates traffic decoder channels in response
@@ -77,30 +75,24 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
* traffic channel allocations.
*
* @param channelModel containing channels currently in use
* @param channelProcessingManager
* @param decodeConfiguration - decoder configuration to use for each
* traffic channel allocation.
* @param recordConfiguration - recording options for each traffic channel
* @param system label to use to describe the system
* @param site label to use to describe the site
* @param aliasListName designated for the channel
* @param trafficChannelTimeout - millisecond call timer limit used when an
* end of call signalling event is not decoded.
* @param trafficChannelPoolSize - maximum number of allocated traffic channels
* in the pool
*/
public TrafficChannelManager(ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
DecodeConfiguration decodeConfiguration,
RecordConfiguration recordConfiguration,
String system,
String site,
String aliasListName,
long trafficChannelTimeout,
int trafficChannelPoolSize)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mDecodeConfiguration = decodeConfiguration;
mRecordConfiguration = recordConfiguration;
mSystem = system;
@@ -123,7 +115,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
mCallEventListener = null;
mDecodeConfiguration = null;
mPreviousDoNotMonitorCallEvent = null;
}
/**
@@ -200,21 +191,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
long frequency = callEvent.getFrequency();
/* Check the from/to aliases for do not monitor priority */
if(isDoNotMonitor(callEvent))
{
callEvent.setCallEventType(CallEventType.CALL_DO_NOT_MONITOR);
if(isSameCallEvent(mPreviousDoNotMonitorCallEvent, callEvent))
{
return;
}
else
{
mPreviousDoNotMonitorCallEvent = callEvent;
}
}
else if(frequency > 0)
if(frequency > 0)
{
Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(Type.TRAFFIC,
frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth()));
@@ -333,31 +310,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider,
return false;
}
/**
* Checks for aliases that contain a do not follow/process priority.
*
* @param event
* @return - true if there is an alias that has a do not process alias id
*/
private boolean isDoNotMonitor(CallEvent event)
{
Alias to = event.getToIDAlias();
if(to != null && to.hasCallPriority() && to.getCallPriority() == Priority.DO_NOT_MONITOR)
{
return true;
}
Alias from = event.getFromIDAlias();
if(from != null && from.hasCallPriority() && from.getCallPriority() == Priority.DO_NOT_MONITOR)
{
return true;
}
return false;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{

View File

@@ -1,5 +1,9 @@
package controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.ThreadPool;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
@@ -8,24 +12,23 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public class NamingThreadFactory implements ThreadFactory
{
static final AtomicInteger poolNumber = new AtomicInteger(1);
private final static Logger mLog = LoggerFactory.getLogger(ThreadPool.class);
private static final AtomicInteger mPoolNumber = new AtomicInteger(1);
final ThreadGroup mThreadGroup;
private final ThreadGroup mThreadGroup;
final AtomicInteger mThreadNumber = new AtomicInteger(1);
private final AtomicInteger mThreadNumber = new AtomicInteger(1);
final String mNamePrefix;
private final String mNamePrefix;
public NamingThreadFactory( String prefix )
{
SecurityManager s = System.getSecurityManager();
mThreadGroup = (s != null)? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
mThreadGroup = (s != null)? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
mNamePrefix = prefix + " pool-" +
poolNumber.getAndIncrement() +
"-thread-";
mNamePrefix = prefix + " pool-" + mPoolNumber.getAndIncrement() + "-thread-";
}
public Thread newThread( Runnable runnable )
@@ -42,7 +45,16 @@ public class NamingThreadFactory implements ThreadFactory
{
thread.setPriority( Thread.NORM_PRIORITY );
}
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
mLog.error("Error while executing runnable in scheduled thread pool [" + t.getName() + "]", e);
}
});
return thread;
}
}

View File

@@ -1,28 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This 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/>
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 controller.channel;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import controller.config.Configuration;
import module.decode.DecoderFactory;
import module.decode.DecoderType;
import module.decode.config.AuxDecodeConfiguration;
@@ -31,6 +27,7 @@ import module.log.EventLogType;
import module.log.config.EventLogConfiguration;
import record.RecorderType;
import record.config.RecordConfiguration;
import sample.Listener;
import source.SourceType;
import source.config.SourceConfigFactory;
import source.config.SourceConfigRecording;
@@ -40,424 +37,444 @@ import source.tuner.TunerChannel;
import source.tuner.TunerChannel.Type;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeProcessor;
import controller.config.Configuration;
@XmlSeeAlso( { Configuration.class } )
@XmlRootElement( name = "channel" )
public class Channel extends Configuration implements IFrequencyChangeProcessor
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
@XmlSeeAlso({Configuration.class})
@XmlRootElement(name = "channel")
public class Channel extends Configuration implements Listener<FrequencyChangeEvent>
{
// Standard channels are persisted and traffic channels are temporary
public enum ChannelType { STANDARD, TRAFFIC };
// Standard channels are persisted and traffic channels are temporary
public enum ChannelType
{
STANDARD, TRAFFIC
}
// Static unique channel identifier tracking
private static int UNIQUE_ID = 0;
;
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
private AuxDecodeConfiguration mAuxDecodeConfiguration =
new AuxDecodeConfiguration();
private SourceConfiguration mSourceConfiguration =
SourceConfigFactory.getDefaultSourceConfiguration();
private EventLogConfiguration mEventLogConfiguration =
new EventLogConfiguration();
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
private String mAliasListName;
private String mSystem = "System";
// Static unique channel identifier tracking
private static int UNIQUE_ID = 0;
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
private AuxDecodeConfiguration mAuxDecodeConfiguration =
new AuxDecodeConfiguration();
private SourceConfiguration mSourceConfiguration =
SourceConfigFactory.getDefaultSourceConfiguration();
private EventLogConfiguration mEventLogConfiguration =
new EventLogConfiguration();
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
private String mAliasListName;
private String mSystem = "System";
private String mSite = "Site";
private String mName = "Channel";
private boolean mEnabled;
private boolean mSelected;
private TunerChannel mTunerChannel = null;
private ChannelType mChannelType = ChannelType.STANDARD;
private int mChannelID;
private int mChannelFrequencyCorrection = 0;
/**
* Channel represents a complete set of configurations needed to setup and
* manage a processing chain and/or manage as a set of business objects that
* can be displayed in graphical components.
*
* @param channelName
* @param channelType
*/
public Channel( String channelName, ChannelType channelType )
{
this( channelName );
mChannelType = channelType;
}
private boolean mEnabled;
private boolean mSelected;
private TunerChannel mTunerChannel = null;
/**
* Constructs a new standard channel with the specified channel name
*/
public Channel( String channelName )
{
this();
mName = channelName;
}
private ChannelType mChannelType = ChannelType.STANDARD;
/**
* Constructs a new standard channel with a default name of "Channel"
*/
public Channel()
{
mChannelID = UNIQUE_ID++;
}
private int mChannelID;
private int mChannelFrequencyCorrection = 0;
/**
* Creates a (new) deep copy of this channel
*/
public Channel copyOf()
{
Channel channel = new Channel( mName );
channel.setSystem( mSystem );
channel.setSite( mSite );
channel.setAliasListName( mAliasListName );
/**
* Channel represents a complete set of configurations needed to setup and
* manage a processing chain and/or manage as a set of business objects that
* can be displayed in graphical components.
*
* @param channelName
* @param channelType
*/
public Channel(String channelName, ChannelType channelType)
{
this(channelName);
mChannelType = channelType;
}
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
for( DecoderType auxType: aux.getAuxDecoders() )
{
aux.addAuxDecoder( auxType );
}
channel.setAuxDecodeConfiguration( aux );
channel.setDecodeConfiguration( DecoderFactory.copy( mDecodeConfiguration ) );
EventLogConfiguration log = new EventLogConfiguration();
for( EventLogType logType: mEventLogConfiguration.getLoggers() )
{
log.addLogger( logType );
}
channel.setEventLogConfiguration( log );
/**
* Constructs a new standard channel with the specified channel name
*/
public Channel(String channelName)
{
this();
mName = channelName;
}
RecordConfiguration record = new RecordConfiguration();
for( RecorderType recordType: mRecordConfiguration.getRecorders() )
{
record.addRecorder( recordType );
}
channel.setRecordConfiguration( record );
/**
* Constructs a new standard channel with a default name of "Channel"
*/
public Channel()
{
mChannelID = UNIQUE_ID++;
}
/**
* Creates a (new) deep copy of this channel
*/
public Channel copyOf()
{
Channel channel = new Channel(mName);
channel.setSystem(mSystem);
channel.setSite(mSite);
channel.setAliasListName(mAliasListName);
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
for(DecoderType auxType : aux.getAuxDecoders())
{
aux.addAuxDecoder(auxType);
}
channel.setAuxDecodeConfiguration(aux);
channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
EventLogConfiguration log = new EventLogConfiguration();
for(EventLogType logType : mEventLogConfiguration.getLoggers())
{
log.addLogger(logType);
}
channel.setEventLogConfiguration(log);
RecordConfiguration record = new RecordConfiguration();
for(RecorderType recordType : mRecordConfiguration.getRecorders())
{
record.addRecorder(recordType);
}
channel.setRecordConfiguration(record);
channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
return channel;
}
/**
* Unique identifier for this channel. Value is transient (not persisted).
*/
@XmlTransient
public int getChannelID()
{
return mChannelID;
}
/**
* Indicates if this channel has been selected for prioritized audio output
* and display of processing chain products
*/
@XmlTransient
public boolean isSelected()
{
return mSelected;
}
/**
* Sets selection status of the channel.
*
* NOTE: this method is package private so that only the ChannelSelectionManager
* can toggle the selection state. Use the channel model to broadcast a
* request select/deselect channel event.
*
* @param selected
*/
void setSelected(boolean selected)
{
mSelected = selected;
}
@XmlTransient
public ChannelType getChannelType()
{
return mChannelType;
}
channel.setSourceConfiguration( SourceConfigFactory.copy( mSourceConfiguration ) );
return channel;
}
/**
* Unique identifier for this channel. Value is transient (not persisted).
*/
@XmlTransient
public int getChannelID()
{
return mChannelID;
}
/**
* Indicates if this channel has been selected for prioritized audio output
* and display of processing chain products
*/
@XmlTransient
public boolean isSelected()
{
return mSelected;
}
/**
* Sets selection status of the channel.
*
* NOTE: this method is package private so that only the ChannelSelectionManager
* can toggle the selection state. Use the channel model to broadcast a
* request select/deselect channel event.
*
* @param selected
*/
void setSelected( boolean selected )
{
mSelected = selected;
}
@XmlTransient
public ChannelType getChannelType()
{
return mChannelType;
}
/**
* Returns the owning system for this channel.
*/
@XmlAttribute( name = "system" )
public String getSystem()
{
return mSystem;
}
public void setSystem( String system )
{
mSystem = system;
}
@XmlAttribute(name = "system")
public String getSystem()
{
return mSystem;
}
public boolean hasSystem()
{
return mSystem != null;
}
/**
* Returns the owning site for this channel.
*/
@XmlAttribute( name = "site" )
public String getSite()
{
return mSite;
}
public void setSite( String site )
{
mSite = site;
}
public boolean hasSite()
{
return mSite != null;
}
/**
* Returns the name of this channel.
*/
@XmlAttribute( name = "name" )
public String getName()
{
return mName;
}
/**
* Sets the name of this channel.
*/
public void setName( String name )
{
mName = name;
}
public void setSystem(String system)
{
mSystem = system;
}
public boolean hasSystem()
{
return mSystem != null;
}
/**
* Returns the owning site for this channel.
*/
@XmlAttribute(name = "site")
public String getSite()
{
return mSite;
}
public void setSite(String site)
{
mSite = site;
}
public boolean hasSite()
{
return mSite != null;
}
/**
* Returns the name of this channel.
*/
@XmlAttribute(name = "name")
public String getName()
{
return mName;
}
/**
* Sets the name of this channel.
*/
public void setName(String name)
{
mName = name;
}
/**
* Default display string for this channel: SYSTEM_SITE_NAME
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( hasSystem() ? getSystem() : "SYSTEM" );
sb.append( "_" );
sb.append( hasSite() ? getSite() : "SITE" );
sb.append( "_" );
sb.append( getName() );
return sb.toString();
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(hasSystem() ? getSystem() : "SYSTEM");
sb.append("_");
sb.append(hasSite() ? getSite() : "SITE");
sb.append("_");
sb.append(getName());
/**
* Indicates if this channel is enabled for processing.
*/
@XmlAttribute( name = "enabled" )
public boolean getEnabled()
{
return mEnabled;
}
return sb.toString();
}
/**
* Indicates if this channel will automatically start processing on
* application startup or if this channel is currently processing after
* application startup.
*
* Note: this method is package private and is intended to be managed
* by a co-package central processing manager
*
* @see controller.ChannelProcessingManager
*/
void setEnabled( boolean enabled )
{
mEnabled = enabled;
}
/**
* Indicates if this channel is enabled for processing.
*/
@XmlAttribute(name = "enabled")
public boolean getEnabled()
{
return mEnabled;
}
/**
* Returns the alias list that is used by this channel for looking up alias
* values for the various identifiers produced by the decoder(s).
*/
@XmlElement( name = "alias_list_name" )
public String getAliasListName()
{
return mAliasListName;
}
/**
* Indicates if this channel will automatically start processing on
* application startup or if this channel is currently processing after
* application startup.
*
* Note: this method is package private and is intended to be managed
* by a co-package central processing manager
*
* @see controller.channel.ChannelProcessingManager
*/
void setEnabled(boolean enabled)
{
mEnabled = enabled;
}
/**
* Sets the alias list to be used for looking up the alias values for the
* various identifiers produced by the decoder
*/
public void setAliasListName( String name )
{
mAliasListName = name;
}
/**
* Returns the alias list that is used by this channel for looking up alias
* values for the various identifiers produced by the decoder(s).
*/
@XmlElement(name = "alias_list_name")
public String getAliasListName()
{
return mAliasListName;
}
/**
* Gets the primary decoder configuration used by this channel
*/
@XmlElement( name = "decode_configuration" )
public DecodeConfiguration getDecodeConfiguration()
{
return mDecodeConfiguration;
}
/**
* Sets the alias list to be used for looking up the alias values for the
* various identifiers produced by the decoder
*/
public void setAliasListName(String name)
{
mAliasListName = name;
}
/**
* Sets the primary decoder configuration used by this channel
*/
public void setDecodeConfiguration( DecodeConfiguration config )
{
mDecodeConfiguration = config;
}
/**
* Gets the primary decoder configuration used by this channel
*/
@XmlElement(name = "decode_configuration")
public DecodeConfiguration getDecodeConfiguration()
{
return mDecodeConfiguration;
}
/**
* Gets the aux decoder configuration used by this channel. Aux decoders
* operate on demodulated audio
*/
@XmlElement( name = "aux_decode_configuration" )
public AuxDecodeConfiguration getAuxDecodeConfiguration()
{
return mAuxDecodeConfiguration;
}
/**
* Sets the primary decoder configuration used by this channel
*/
public void setDecodeConfiguration(DecodeConfiguration config)
{
mDecodeConfiguration = config;
}
/**
* Sets the decoder configuration used by this channel
*/
public void setAuxDecodeConfiguration( AuxDecodeConfiguration config )
{
mAuxDecodeConfiguration = config;
}
/**
* Gets the aux decoder configuration used by this channel. Aux decoders
* operate on demodulated audio
*/
@XmlElement(name = "aux_decode_configuration")
public AuxDecodeConfiguration getAuxDecodeConfiguration()
{
return mAuxDecodeConfiguration;
}
/**
* Returns the source configuration for this channel. A channel source
* identifies where the processing chain will get source sample data from.
*/
@XmlElement( name = "source_configuration" )
public SourceConfiguration getSourceConfiguration()
{
return mSourceConfiguration;
}
/**
* Sets the decoder configuration used by this channel
*/
public void setAuxDecodeConfiguration(AuxDecodeConfiguration config)
{
mAuxDecodeConfiguration = config;
}
/**
* Sets the source configuration for this channel.
*/
public void setSourceConfiguration( SourceConfiguration config )
{
mSourceConfiguration = config;
/**
* Returns the source configuration for this channel. A channel source
* identifies where the processing chain will get source sample data from.
*/
@XmlElement(name = "source_configuration")
public SourceConfiguration getSourceConfiguration()
{
return mSourceConfiguration;
}
//Clear the tune channel object so that it can be recreated if the
//source configuration changes
mTunerChannel = null;
}
/**
* Sets the source configuration for this channel.
*/
public void setSourceConfiguration(SourceConfiguration config)
{
mSourceConfiguration = config;
/**
* Returns the event logger configuration for this channel.
*/
@XmlElement( name = "event_log_configuration" )
public EventLogConfiguration getEventLogConfiguration()
{
return mEventLogConfiguration;
}
//Clear the tune channel object so that it can be recreated if the
//source configuration changes
mTunerChannel = null;
}
/**
* Sets the event logger configuration for this channel.
*/
public void setEventLogConfiguration( EventLogConfiguration config )
{
mEventLogConfiguration = config;
}
/**
* Returns the event logger configuration for this channel.
*/
@XmlElement(name = "event_log_configuration")
public EventLogConfiguration getEventLogConfiguration()
{
return mEventLogConfiguration;
}
/**
* Returns the recorder configuration for this channel.
*/
@XmlElement( name = "record_configuration" )
public RecordConfiguration getRecordConfiguration()
{
return mRecordConfiguration;
}
/**
* Sets the event logger configuration for this channel.
*/
public void setEventLogConfiguration(EventLogConfiguration config)
{
mEventLogConfiguration = config;
}
/**
* Sets the recorder configuration for this channel.
*/
public void setRecordConfiguration( RecordConfiguration config )
{
mRecordConfiguration = config;
}
/**
* Returns the recorder configuration for this channel.
*/
@XmlElement(name = "record_configuration")
public RecordConfiguration getRecordConfiguration()
{
return mRecordConfiguration;
}
/**
* Convenience method to construct a tuner channel object representing a
* tuner or recording source frequency and bandwidth that can be used by
* application components for graphically representing this channel.
*
* If the source configuration is not a tuner or recording, this method
* returns null.
*/
@XmlTransient
/**
* Sets the recorder configuration for this channel.
*/
public void setRecordConfiguration(RecordConfiguration config)
{
mRecordConfiguration = config;
}
/**
* Convenience method to construct a tuner channel object representing a
* tuner or recording source frequency and bandwidth that can be used by
* application components for graphically representing this channel.
*
* If the source configuration is not a tuner or recording, this method
* returns null.
*/
@XmlTransient
public TunerChannel getTunerChannel()
{
if( mTunerChannel == null )
{
if( mSourceConfiguration.getSourceType() == SourceType.TUNER )
{
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
if(mTunerChannel == null)
{
if(mSourceConfiguration.getSourceType() == SourceType.TUNER)
{
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
}
else if( mSourceConfiguration.getSourceType() == SourceType.RECORDING )
{
SourceConfigRecording config =
(SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
}
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
{
SourceConfigRecording config =
(SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth());
}
}
mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(),
mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
}
}
return mTunerChannel;
}
/**
* Convenience method to indicate if any part of this channel is contained
* within the minimum and maximum frequency values.
*/
public boolean isWithin( long minimum, long maximum )
{
TunerChannel tunerChannel = getTunerChannel();
return tunerChannel != null &&
tunerChannel.overlaps( minimum, maximum );
}
/**
* Convenience method to make the current channel frequency correction value
* available for use outside of a constructed processing chain while maintaining
* linkage to the source channel. This hack allows the correction value to
* be used to visually show in the spectral display.
*/
@Override
public void frequencyChanged( FrequencyChangeEvent event )
{
if( event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE )
{
mChannelFrequencyCorrection = event.getValue().intValue();
}
}
/**
* Convenience method to indicate if any part of this channel is contained
* within the minimum and maximum frequency values.
*/
public boolean isWithin(long minimum, long maximum)
{
TunerChannel tunerChannel = getTunerChannel();
@XmlTransient
public int getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
}
/**
* Convenience method to make the current channel frequency correction value
* available for use outside of a constructed processing chain while maintaining
* linkage to the source channel. This hack allows the correction value to
* be used to visually show in the spectral display.
*/
@Override
public void receive(FrequencyChangeEvent event)
{
if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
{
mChannelFrequencyCorrection = event.getValue().intValue();
}
}
/**
* Current channel frequency correction value (when this channel is processing)
* @return
*/
@XmlTransient
public int getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Resets frequency correction value to 0
*/
public void resetFrequencyCorrection()
{
mChannelFrequencyCorrection = 0;
}
}

View File

@@ -231,7 +231,7 @@ public class ChannelProcessingManager implements ChannelEventListener
if(processingChain == null)
{
processingChain = new ProcessingChain(channel.getName(), channel.getChannelType());
processingChain = new ProcessingChain(channel.getChannelType());
/* Register global listeners */
for(Listener<AudioPacket> listener : mAudioPacketListeners)
@@ -244,6 +244,9 @@ public class ChannelProcessingManager implements ChannelEventListener
processingChain.addMessageListener(listener);
}
//Register channel to receive frequency correction events to show in the spectral display (hack!)
processingChain.addFrequencyChangeListener(channel);
/* Processing Modules */
List<Module> modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this,
mAliasModel, channel, processingChain.getChannelState().getMutableMetadata());
@@ -347,6 +350,9 @@ public class ChannelProcessingManager implements ChannelEventListener
processingChain.removeRecordingModules();
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
processingChain.removeFrequencyChangeListener(channel);
channel.resetFrequencyCorrection();
mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP));

View File

@@ -1,269 +1,267 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
*
* This 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 controller.channel;
import alias.AliasModel;
import gui.editor.Editor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.List;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.miginfocom.swing.MigLayout;
import alias.AliasModel;
public class NameConfigurationEditor extends Editor<Channel> implements DocumentListener
{
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger( NameConfigurationEditor.class );
private final static Logger mLog = LoggerFactory.getLogger(NameConfigurationEditor.class);
private static ComboBoxModel<String> EMPTY_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_SYSTEM_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_SITE_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_ALIAS_LIST_MODEL = new DefaultComboBoxModel<>();
private AliasModel mAliasModel;
private ChannelModel mChannelModel;
private JTextField mChannelName;
private JComboBox<String> mSystemNameCombo;
private JComboBox<String> mSiteNameCombo;
private JComboBox<String> mAliasListCombo;
public NameConfigurationEditor( AliasModel aliasModel, ChannelModel model )
{
mAliasModel = aliasModel;
mChannelModel = model;
private AliasModel mAliasModel;
private ChannelModel mChannelModel;
init();
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]" ) );
private JTextField mChannelName;
private JComboBox<String> mSystemNameCombo;
private JComboBox<String> mSiteNameCombo;
private JComboBox<String> mAliasListCombo;
mChannelName = new JTextField( "" );
mChannelName.getDocument().addDocumentListener( this );
mChannelName.setEnabled( false );
public NameConfigurationEditor(AliasModel aliasModel, ChannelModel model)
{
mAliasModel = aliasModel;
mChannelModel = model;
add( new JLabel( "Name:" ) );
add( mChannelName );
init();
}
mSystemNameCombo = new JComboBox<>( EMPTY_MODEL );
mSystemNameCombo.setEnabled( false );
mSystemNameCombo.setEditable( true );
mSystemNameCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
Object selected = mSystemNameCombo.getSelectedItem();
if( selected != null )
{
String system = (String)selected;
List<String> sites = mChannelModel.getSites( system );
private void init()
{
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]"));
if( !sites.contains( getItem().getSite() ) )
{
sites.add( getItem().getSite() );
}
if( sites.isEmpty() )
{
mSiteNameCombo.setModel( EMPTY_MODEL );
}
else
{
mSiteNameCombo.setModel( new DefaultComboBoxModel<String>(
sites.toArray( new String[ sites.size() ] ) ) );;
}
mSiteNameCombo.setSelectedItem( getItem().getSite() );
}
setModified( true );
}
} );
add( new JLabel( "System:" ) );
add( mSystemNameCombo );
mChannelName = new JTextField("");
mChannelName.getDocument().addDocumentListener(this);
mChannelName.setEnabled(false);
mAliasListCombo = new JComboBox<String>();
mAliasListCombo.setEnabled( false );
mAliasListCombo.setEditable( true );
mAliasListCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( new JLabel( "Alias List:" ) );
add( mAliasListCombo );
mSiteNameCombo = new JComboBox<>( EMPTY_MODEL );
mSiteNameCombo.setEnabled( false );
mSiteNameCombo.setEditable( true );
mSiteNameCombo.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
setModified( true );
}
} );
add( new JLabel( "Site:" ) );
add( mSiteNameCombo );
}
@Override
public void save()
{
if( hasItem() && isModified() )
{
getItem().setName( mChannelName.getText() );
Object system = mSystemNameCombo.getSelectedItem();
getItem().setSystem( ( system == null ? null : (String)system ) );
add(new JLabel("Name:"));
add(mChannelName);
Object site = mSiteNameCombo.getSelectedItem();
getItem().setSite( ( site == null ? null : (String)site ) );
mSystemNameCombo = new JComboBox<>(EMPTY_SYSTEM_MODEL);
mSystemNameCombo.setEnabled(false);
mSystemNameCombo.setEditable(true);
mSystemNameCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
Object selected = mSystemNameCombo.getSelectedItem();
Object aliasList = mAliasListCombo.getSelectedItem();
getItem().setAliasListName( aliasList == null ? null : (String)aliasList );
}
setModified( false );
}
if(selected != null)
{
String system = (String)selected;
private void setControlsEnabled( boolean enabled )
{
if( mChannelName.isEnabled() != enabled )
{
mChannelName.setEnabled( enabled );
}
List<String> sites = mChannelModel.getSites(system);
if( mAliasListCombo.isEnabled() != enabled )
{
mAliasListCombo.setEnabled( enabled );
}
if(!sites.contains(getItem().getSite()))
{
sites.add(getItem().getSite());
}
if( mSystemNameCombo.isEnabled() != enabled )
{
mSystemNameCombo.setEnabled( enabled );
}
if( mSiteNameCombo.isEnabled() != enabled )
{
mSiteNameCombo.setEnabled( enabled );
}
}
if(sites.isEmpty())
{
mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
}
else
{
mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
sites.toArray(new String[sites.size()])));
;
}
@Override
public void setItem( Channel channel )
{
super.setItem( channel );
if( hasItem() )
{
setControlsEnabled( true );
mChannelName.setText( channel.getName() );
mSiteNameCombo.setSelectedItem(getItem().getSite());
}
List<String> systems = mChannelModel.getSystems();
if( systems.isEmpty() )
{
mSystemNameCombo.setModel( EMPTY_MODEL );
}
else
{
mSystemNameCombo.setModel( new DefaultComboBoxModel<String>(
systems.toArray( new String[ systems.size() ] ) ) );;
}
mSystemNameCombo.setSelectedItem( channel.getSystem() );
setModified(true);
}
});
List<String> sites = mChannelModel.getSites( channel.getSystem() );
add(new JLabel("System:"));
add(mSystemNameCombo);
if( sites.isEmpty() )
{
mSiteNameCombo.setModel( EMPTY_MODEL );
}
else
{
mSiteNameCombo.setModel( new DefaultComboBoxModel<String>(
sites.toArray( new String[ sites.size() ] ) ) );;
}
mAliasListCombo = new JComboBox<String>(EMPTY_ALIAS_LIST_MODEL);
mAliasListCombo.setEnabled(false);
mAliasListCombo.setEditable(true);
mAliasListCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
if( channel.getSite() != null )
{
mSiteNameCombo.setSelectedItem( channel.getSite() );
}
List<String> lists = mAliasModel.getListNames();
Collections.sort( lists );
mAliasListCombo.setModel( new DefaultComboBoxModel<String>(
lists.toArray( new String[ lists.size() ] ) ) );
if( channel.getAliasListName() != null )
{
mAliasListCombo.setSelectedItem( channel.getAliasListName() );
}
}
else
{
setControlsEnabled( false );
mChannelName.setText( null );
}
setModified( false );
}
add(new JLabel("Alias List:"));
add(mAliasListCombo);
@Override
public void insertUpdate( DocumentEvent e )
{
setModified( true );
}
mSiteNameCombo = new JComboBox<>(EMPTY_SITE_MODEL);
mSiteNameCombo.setEnabled(false);
mSiteNameCombo.setEditable(true);
mSiteNameCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
@Override
public void changedUpdate( DocumentEvent e )
{
setModified( true );
}
@Override
public void removeUpdate( DocumentEvent e )
{
setModified( true );
}
add(new JLabel("Site:"));
add(mSiteNameCombo);
}
@Override
public void save()
{
if(hasItem() && isModified())
{
getItem().setName(mChannelName.getText());
Object system = mSystemNameCombo.getSelectedItem();
getItem().setSystem((system == null ? null : (String)system));
Object site = mSiteNameCombo.getSelectedItem();
getItem().setSite((site == null ? null : (String)site));
Object aliasList = mAliasListCombo.getSelectedItem();
getItem().setAliasListName(aliasList == null ? null : (String)aliasList);
}
setModified(false);
}
private void setControlsEnabled(boolean enabled)
{
if(mChannelName.isEnabled() != enabled)
{
mChannelName.setEnabled(enabled);
}
if(mAliasListCombo.isEnabled() != enabled)
{
mAliasListCombo.setEnabled(enabled);
}
if(mSystemNameCombo.isEnabled() != enabled)
{
mSystemNameCombo.setEnabled(enabled);
}
if(mSiteNameCombo.isEnabled() != enabled)
{
mSiteNameCombo.setEnabled(enabled);
}
}
@Override
public void setItem(Channel channel)
{
super.setItem(channel);
if(hasItem())
{
setControlsEnabled(true);
mChannelName.setText(channel.getName());
List<String> systems = mChannelModel.getSystems();
if(systems.isEmpty())
{
mSystemNameCombo.setModel(EMPTY_SYSTEM_MODEL);
}
else
{
mSystemNameCombo.setModel(new DefaultComboBoxModel<String>(
systems.toArray(new String[systems.size()])));
;
}
mSystemNameCombo.setSelectedItem(channel.getSystem());
List<String> sites = mChannelModel.getSites(channel.getSystem());
if(sites.isEmpty())
{
mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
}
else
{
mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
sites.toArray(new String[sites.size()])));
;
}
if(channel.getSite() != null)
{
mSiteNameCombo.setSelectedItem(channel.getSite());
}
List<String> lists = mAliasModel.getListNames();
Collections.sort(lists);
mAliasListCombo.setModel(new DefaultComboBoxModel<String>(
lists.toArray(new String[lists.size()])));
if(channel.getAliasListName() != null)
{
mAliasListCombo.setSelectedItem(channel.getAliasListName());
}
}
else
{
setControlsEnabled(false);
mChannelName.setText(null);
}
setModified(false);
}
@Override
public void insertUpdate(DocumentEvent e)
{
setModified(true);
}
@Override
public void changedUpdate(DocumentEvent e)
{
setModified(true);
}
@Override
public void removeUpdate(DocumentEvent e)
{
setModified(true);
}
}

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener;
public class FloatFIRFilter implements RealSampleListener
@@ -139,7 +140,7 @@ public class FloatFIRFilter implements RealSampleListener
private void generateIndexMap( int size )
{
//Ensure we have an odd size
assert( size % 2 == 1 );
Validate.isTrue(size % 2 == 1);
mIndexMap = new int[ size ][ size ];

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener;
public class FloatHalfBandFilter implements RealSampleListener
@@ -149,7 +150,7 @@ public class FloatHalfBandFilter implements RealSampleListener
private void generateIndexMap( int size )
{
//Ensure we have an odd size
assert( size % 2 == 1 );
Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -19,6 +19,7 @@ package dsp.filter;
import java.util.ArrayList;
import org.apache.commons.lang3.Validate;
import sample.real.RealSampleListener;
public class FloatHalfBandNoDecimateFilter implements RealSampleListener
@@ -139,7 +140,7 @@ public class FloatHalfBandNoDecimateFilter implements RealSampleListener
private void generateIndexMap( int size )
{
//Ensure we have an odd size
assert( size % 2 == 1 );
Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -1,407 +1,412 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2015 Dennis Sheirer
*
* This 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/>
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 dsp.filter.cic;
import java.util.ArrayList;
import java.util.List;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexSampleListener;
import sample.complex.ComplexToComplexBufferAssembler;
import sample.decimator.ComplexDecimator;
import dsp.filter.FilterFactory;
import dsp.filter.Filters;
import dsp.filter.Window.WindowType;
import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.filter.halfband.complex.HalfBandFilter_CB_CB;
import org.apache.commons.lang3.Validate;
import sample.Listener;
import sample.complex.ComplexBuffer;
import sample.complex.ComplexSampleListener;
import sample.complex.ComplexToComplexBufferAssembler;
import sample.decimator.ComplexDecimator;
import java.util.ArrayList;
import java.util.List;
public class ComplexPrimeCICDecimate implements Listener<ComplexBuffer>
{
public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,
157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,
257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,
367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,
467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,
599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 };
private ArrayList<DecimatingStage> mDecimatingStages =
new ArrayList<DecimatingStage>();
private DecimatingStage mFirstDecimatingStage;
public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251,
257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359,
367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593,
599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691};
private Output mOutput;
private ArrayList<DecimatingStage> mDecimatingStages =
new ArrayList<DecimatingStage>();
/**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated, single paired i/q float sample output.
*
* Implements the CIC filter described in Understanding Digital Signal
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
* decimating stages each with a prime factor decimation rate. Multiple
* stages are cascaded to achieve the overall decimation rate.
*
* This filter supports a maximum decimation rate of 700. This filter can
* be adapted to higher decimation rates by adding additional prime factors
* to the PRIMES array.
*
* @param decimation - overall decimation rate
* @param order - filter order
*/
public ComplexPrimeCICDecimate( int decimation, int order,
int passFrequency, int attenuation, WindowType windowType )
{
assert( decimation <= 700 );
private DecimatingStage mFirstDecimatingStage;
List<Integer> stageSizes = getPrimeFactors( decimation );
for( int x = 0; x < stageSizes.size(); x++ )
{
DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order );
mDecimatingStages.add( stage );
if( x == 0 )
{
/* Reference to first stage -- will receive all samples */
mFirstDecimatingStage = stage;
}
else
{
/* Wire the current stage to the previous stage */
mDecimatingStages.get( x - 1 ).setListener( stage );
}
}
mOutput = new Output( 48000, passFrequency, attenuation, windowType );
mDecimatingStages.get( mDecimatingStages.size() - 1 )
.setListener( mOutput );
}
public void dispose()
{
for( DecimatingStage stage: mDecimatingStages )
{
stage.dispose();
}
mDecimatingStages.clear();
mDecimatingStages = null;
mFirstDecimatingStage = null;
mOutput.dispose();
mOutput = null;
}
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener( Listener<ComplexBuffer> listener )
{
mOutput.setListener( listener );
}
/**
* Removes listener from output of this CIC decimation filter
*/
public void removeListener()
{
mOutput.removeListener();
}
private Output mOutput;
/**
* Calculates the prime factors of the decimation rate up to a maximum
* decimation rate of 700. If you wish to have decimation rates higher
* than 700, then add additional prime factors to the PRIMES array.
*
* @param decimation - integral decimation rate
* @return - ordered list (smallest to largest) of prime factors
*/
public static List<Integer> getPrimeFactors( int decimation )
{
ArrayList<Integer> stages = new ArrayList<Integer>();
int pointer = 0;
while( decimation > 0 && pointer < PRIMES.length )
{
int prime = PRIMES[ pointer ];
if( decimation % prime == 0 )
{
stages.add( prime );
decimation /= prime;
}
else
{
pointer++;
}
}
return stages;
}
/**
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
*/
@Override
public void receive( ComplexBuffer buffer )
/**
* Non-Recursive Prime-Factor CIC Filter with float sample array inputs and
* decimated, single paired i/q float sample output.
*
* Implements the CIC filter described in Understanding Digital Signal
* Processing, 3e, Lyons, on page 769. This filter is comprised of multiple
* decimating stages each with a prime factor decimation rate. Multiple
* stages are cascaded to achieve the overall decimation rate.
*
* This filter supports a maximum decimation rate of 700. This filter can
* be adapted to higher decimation rates by adding additional prime factors
* to the PRIMES array.
*
* @param decimation - overall decimation rate
* @param order - filter order
*/
public ComplexPrimeCICDecimate(int decimation, int order,
int passFrequency, int attenuation, WindowType windowType)
{
if( mFirstDecimatingStage != null )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x += 2 )
{
mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] );
}
}
}
/**
* Decimating stage combines multiple CIC stages with a decimator. The
* number of stages is indicated by the order value and the size indicates
* the decimation rate of this stage.
*/
public class DecimatingStage implements ComplexSampleListener
{
private ArrayList<Stage> mStages = new ArrayList<Stage>();
private Stage mFirstStage;
private ComplexDecimator mDecimator;
public DecimatingStage( int size, int order )
{
for( int x = 0; x < order; x++ )
{
Stage stage;
if( size == 2 )
{
stage = new TwoStage();
}
else
{
stage = new Stage( size );
}
mStages.add( stage );
if( x == 0 )
{
mFirstStage = stage;
}
else
{
mStages.get( x - 1 ).setListener( stage );
}
}
mDecimator = new ComplexDecimator( size );
mStages.get( mStages.size() - 1 ).setListener( mDecimator );
}
public void dispose()
{
for( Stage stage: mStages )
{
stage.dispose();
}
mStages.clear();
mDecimator.dispose();
mDecimator = null;
mFirstStage = null;
mStages = null;
}
Validate.isTrue(decimation <= 700);
@Override
public void receive( float i, float q )
List<Integer> stageSizes = getPrimeFactors(decimation);
for(int x = 0; x < stageSizes.size(); x++)
{
mFirstStage.receive( i, q );
}
public void setListener( ComplexSampleListener listener )
{
mDecimator.setListener( listener );
}
}
/**
* Single non-decimating CIC stage component. Uses a circular buffer and a
* running average internally to implement the stage so that stage size has
* essentially no impact on the computational requirements of the stage
*/
public class Stage implements ComplexSampleListener
{
protected ComplexSampleListener mListener;
private float[] mISamples;
private float[] mQSamples;
protected float mISum;
protected float mQSum;
private int mSamplePointer = 0;
private int mSize;
protected float mGain;
public Stage()
{
}
DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order);
public Stage( int size )
{
mSize = size - 1;
mISamples = new float[ mSize ];
mQSamples = new float[ mSize ];
mGain = 1.0f / (float)size;
}
public void dispose()
{
mListener = null;
}
public void receive( float i, float q )
{
mDecimatingStages.add(stage);
if(x == 0)
{
/* Reference to first stage -- will receive all samples */
mFirstDecimatingStage = stage;
}
else
{
/* Wire the current stage to the previous stage */
mDecimatingStages.get(x - 1).setListener(stage);
}
}
mOutput = new Output(48000, passFrequency, attenuation, windowType);
mDecimatingStages.get(mDecimatingStages.size() - 1)
.setListener(mOutput);
}
public void dispose()
{
for(DecimatingStage stage : mDecimatingStages)
{
stage.dispose();
}
mDecimatingStages.clear();
mDecimatingStages = null;
mFirstDecimatingStage = null;
mOutput.dispose();
mOutput = null;
}
/**
* Adds a listener to receive the output of this CIC decimation filter
*/
public void setListener(Listener<ComplexBuffer> listener)
{
mOutput.setListener(listener);
}
/**
* Removes listener from output of this CIC decimation filter
*/
public void removeListener()
{
mOutput.removeListener();
}
/**
* Calculates the prime factors of the decimation rate up to a maximum
* decimation rate of 700. If you wish to have decimation rates higher
* than 700, then add additional prime factors to the PRIMES array.
*
* @param decimation - integral decimation rate
* @return - ordered list (smallest to largest) of prime factors
*/
public static List<Integer> getPrimeFactors(int decimation)
{
ArrayList<Integer> stages = new ArrayList<Integer>();
int pointer = 0;
while(decimation > 0 && pointer < PRIMES.length)
{
int prime = PRIMES[pointer];
if(decimation % prime == 0)
{
stages.add(prime);
decimation /= prime;
}
else
{
pointer++;
}
}
return stages;
}
/**
* Primary input method for receiving sample arrays composed as I,Q,I,Q, etc.
*/
@Override
public void receive(ComplexBuffer buffer)
{
if(mFirstDecimatingStage != null)
{
float[] samples = buffer.getSamples();
for(int x = 0; x < samples.length; x += 2)
{
mFirstDecimatingStage.receive(samples[x], samples[x + 1]);
}
}
}
/**
* Decimating stage combines multiple CIC stages with a decimator. The
* number of stages is indicated by the order value and the size indicates
* the decimation rate of this stage.
*/
public class DecimatingStage implements ComplexSampleListener
{
private ArrayList<Stage> mStages = new ArrayList<Stage>();
private Stage mFirstStage;
private ComplexDecimator mDecimator;
public DecimatingStage(int size, int order)
{
for(int x = 0; x < order; x++)
{
Stage stage;
if(size == 2)
{
stage = new TwoStage();
}
else
{
stage = new Stage(size);
}
mStages.add(stage);
if(x == 0)
{
mFirstStage = stage;
}
else
{
mStages.get(x - 1).setListener(stage);
}
}
mDecimator = new ComplexDecimator(size);
mStages.get(mStages.size() - 1).setListener(mDecimator);
}
public void dispose()
{
for(Stage stage : mStages)
{
stage.dispose();
}
mStages.clear();
mDecimator.dispose();
mDecimator = null;
mFirstStage = null;
mStages = null;
}
@Override
public void receive(float i, float q)
{
mFirstStage.receive(i, q);
}
public void setListener(ComplexSampleListener listener)
{
mDecimator.setListener(listener);
}
}
/**
* Single non-decimating CIC stage component. Uses a circular buffer and a
* running average internally to implement the stage so that stage size has
* essentially no impact on the computational requirements of the stage
*/
public class Stage implements ComplexSampleListener
{
protected ComplexSampleListener mListener;
private float[] mISamples;
private float[] mQSamples;
protected float mISum;
protected float mQSum;
private int mSamplePointer = 0;
private int mSize;
protected float mGain;
public Stage()
{
}
public Stage(int size)
{
mSize = size - 1;
mISamples = new float[mSize];
mQSamples = new float[mSize];
mGain = 1.0f / (float)size;
}
public void dispose()
{
mListener = null;
}
public void receive(float i, float q)
{
/* Subtract the oldest sample and add back in the newest */
mISum = mISum - mISamples[ mSamplePointer ] + i;
mQSum = mQSum - mQSamples[ mSamplePointer ] + q;
mISum = mISum - mISamples[mSamplePointer] + i;
mQSum = mQSum - mQSamples[mSamplePointer] + q;
/* Overwrite the oldest sample with the newest */
mISamples[ mSamplePointer ] = i;
mQSamples[ mSamplePointer ] = q;
mSamplePointer++;
if( mSamplePointer >= mSize )
{
mSamplePointer = 0;
}
mISamples[mSamplePointer] = i;
mQSamples[mSamplePointer] = q;
if( mListener != null )
{
mListener.receive( ( mISum * mGain ), ( mQSum * mGain ) );
}
}
public void setListener( ComplexSampleListener listener )
{
mListener = listener;
}
}
/**
* Size 2 stage that removes the unnecessary circular buffer management for
* a two-stage.
*/
public class TwoStage extends Stage
{
public TwoStage()
{
super();
mGain = 0.5f;
}
public void receive( float i, float q )
{
float iSum = mISum + i;
float qSum = mQSum + q;
mISum = i;
mQSum = q;
if( mListener != null )
{
mListener.receive( ( iSum * mGain ), ( qSum * mGain ) );
}
}
}
mSamplePointer++;
/**
* Output adapter - applies gain correction and cleanup filter and broadcast
* to registered listener.
*/
public class Output implements ComplexSampleListener
{
/* Decimated output buffers will contain 1024 samples */
private ComplexToComplexBufferAssembler mAssembler =
new ComplexToComplexBufferAssembler( 2048 );
if(mSamplePointer >= mSize)
{
mSamplePointer = 0;
}
private ComplexFIRFilter_CB_CB mCleanupFilter;
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false );
public Output( int outputSampleRate, int passFrequency, int attenuation,
WindowType windowType )
{
mCleanupFilter = new ComplexFIRFilter_CB_CB( FilterFactory
.getCICCleanupFilter( outputSampleRate,
passFrequency,
attenuation,
windowType ), 0.4f );
mAssembler.setListener( mCleanupFilter );
mCleanupFilter.setListener( mHalfBandFilter );
}
public void dispose()
{
mAssembler.dispose();
mCleanupFilter.dispose();
mHalfBandFilter.dispose();
}
/**
* Receiver method for the output adapter to receive a filtered,
* decimated sample, apply gain correction, apply cleanup filtering
* and output the sample values as a complex sample.
*/
@Override
public void receive( float inphase, float quadrature )
{
mAssembler.receive( inphase, quadrature );
}
/**
* Adds a listener to receive output samples
*/
public void setListener( Listener<ComplexBuffer> listener )
{
mHalfBandFilter.setListener( listener );
if(mListener != null)
{
mListener.receive((mISum * mGain), (mQSum * mGain));
}
}
/**
* Removes the listener from receiving output samples
*/
public void setListener(ComplexSampleListener listener)
{
mListener = listener;
}
}
/**
* Size 2 stage that removes the unnecessary circular buffer management for
* a two-stage.
*/
public class TwoStage extends Stage
{
public TwoStage()
{
super();
mGain = 0.5f;
}
public void receive(float i, float q)
{
float iSum = mISum + i;
float qSum = mQSum + q;
mISum = i;
mQSum = q;
if(mListener != null)
{
mListener.receive((iSum * mGain), (qSum * mGain));
}
}
}
/**
* Output adapter - applies gain correction and cleanup filter and broadcast
* to registered listener.
*/
public class Output implements ComplexSampleListener
{
/* Decimated output buffers will contain 1024 samples */
private ComplexToComplexBufferAssembler mAssembler =
new ComplexToComplexBufferAssembler(2048);
private ComplexFIRFilter_CB_CB mCleanupFilter;
private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB(
Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false);
public Output(int outputSampleRate, int passFrequency, int attenuation,
WindowType windowType)
{
mCleanupFilter = new ComplexFIRFilter_CB_CB(FilterFactory
.getCICCleanupFilter(outputSampleRate,
passFrequency,
attenuation,
windowType), 0.4f);
//Bypassing the CIC cleanup filter for now
mAssembler.setListener(mHalfBandFilter);
// mAssembler.setListener( mCleanupFilter );
// mCleanupFilter.setListener( mHalfBandFilter );
}
public void dispose()
{
mAssembler.dispose();
mCleanupFilter.dispose();
mHalfBandFilter.dispose();
}
/**
* Receiver method for the output adapter to receive a filtered,
* decimated sample, apply gain correction, apply cleanup filtering
* and output the sample values as a complex sample.
*/
@Override
public void receive(float inphase, float quadrature)
{
mAssembler.receive(inphase, quadrature);
}
/**
* Adds a listener to receive output samples
*/
public void setListener(Listener<ComplexBuffer> listener)
{
mHalfBandFilter.setListener(listener);
}
/**
* Removes the listener from receiving output samples
*/
public void removeListener()
{
mHalfBandFilter.removeListener();
mHalfBandFilter.removeListener();
}
}
}
}

View File

@@ -20,6 +20,7 @@ package dsp.filter.cic;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -81,7 +82,7 @@ public class RealPrimeCICDecimate
public RealPrimeCICDecimate( int decimation, int order, int passFrequency,
int attenuation, WindowType windowType, int outputBufferSize )
{
assert( decimation <= 700 );
Validate.isTrue(decimation <= 700);
mOutputBufferSize = outputBufferSize;

View File

@@ -1,65 +1,64 @@
package dsp.filter.design;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dsp.filter.fir.FIRFilterSpecification;
import dsp.filter.fir.remez.RemezFIRFilterDesigner;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FilterViewer extends Application
{
private final static Logger mLog = LoggerFactory.getLogger( FilterViewer.class );
private final static Logger mLog = LoggerFactory.getLogger(FilterViewer.class);
/**
* Developer tool to visualize filter designs
*/
public FilterViewer()
{
}
@Override
public void start( Stage primaryStage ) throws Exception
{
Scene scene = new Scene( new FilterView( getFilter() ) );
primaryStage.setTitle( "Filter Viewer" );
primaryStage.setScene( scene );
primaryStage.show();
}
public static void main( String[] args )
{
launch( args );
}
/**
* Developer tool to visualize filter designs
*/
public FilterViewer()
{
}
/**
* Provides the filter to visualize. Modify this method to visualize your filter design.
*/
private float[] getFilter()
{
// FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
// .sampleRate( 48000 )
// .gridDensity( 16 )
// .passBandCutoff( 3200 )
// .passBandAmplitude( 1.0 )
// .passBandRipple( 0.02 )
// .stopBandStart( 4000 )
// .stopBandAmplitude( 0.0 )
// .stopBandRipple( 0.03 )
// .build();
FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
.sampleRate( 24000 )
.stopBandCutoff( 800 )
.stopBandAmplitude( 0.0 )
.stopBandRipple( 0.03 )
.passBandStart( 1000 )
.passBandAmplitude( 1.0 )
.passBandRipple( 0.08 )
.build();
@Override
public void start(Stage primaryStage) throws Exception
{
Scene scene = new Scene(new FilterView(getFilter()));
primaryStage.setTitle("Filter Viewer");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
/**
* Provides the filter to visualize. Modify this method to visualize your filter design.
*/
private float[] getFilter()
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate(48000)
.gridDensity(16)
.passBandCutoff(2500)
.passBandAmplitude(1.0)
.passBandRipple(0.01)
.stopBandStart(4000)
.stopBandAmplitude(0.0)
.stopBandRipple(0.008)
.build();
// FIRFilterSpecification specification = FIRFilterSpecification.highPassBuilder()
// .sampleRate(24000)
// .stopBandCutoff(800)
// .stopBandAmplitude(0.0)
// .stopBandRipple(0.03)
// .passBandStart(1000)
// .passBandAmplitude(1.0)
// .passBandRipple(0.08)
// .build();
// FIRFilterSpecification specification = FIRFilterSpecification.bandPassBuilder()
// .sampleRate( 48000 )
@@ -71,24 +70,25 @@ public class FilterViewer extends Application
// .passRipple( 0.008)
// .build();
float[] taps = null;
float[] taps = null;
// taps = FilterFactory.getLowPass( 16000, 2400, 73, WindowType.HANNING );
// taps = FilterFactory.getLowPass(48000, 6750, 7500, 60,
// Window.WindowType.HAMMING, true);
try
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner( specification );
if( designer.isValid() )
{
taps = designer.getImpulseResponse();
}
}
catch( FilterDesignException fde )
{
mLog.error( "Filter design error", fde );
}
return taps;
}
try
{
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
if(designer.isValid())
{
taps = designer.getImpulseResponse();
}
}
catch(FilterDesignException fde)
{
mLog.error("Filter design error", fde);
}
return taps;
}
}

View File

@@ -17,6 +17,8 @@
******************************************************************************/
package dsp.filter.halfband.real;
import org.apache.commons.lang3.Validate;
public class HalfBandFilter
{
private float[] mBuffer;
@@ -122,7 +124,7 @@ public class HalfBandFilter
private void generateIndexMap( int size )
{
//Ensure we have an odd size
assert( size % 2 == 1 );
Validate.isTrue(size % 2 == 1);
int mapWidth = ( ( size + 1 ) / 2 ) + 1;

View File

@@ -1,5 +1,6 @@
package dsp.filter.interpolator;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,7 +32,7 @@ public class RealInterpolator extends Interpolator
public float filter( float[] samples, int offset, float mu )
{
/* Ensure we have enough samples in the array */
assert( samples.length >= offset + 7 );
Validate.isTrue(samples.length >= offset + 7);
/* Identify the filter bank that corresponds to mu */
int index = (int)( NSTEPS * mu );

View File

@@ -18,6 +18,7 @@
******************************************************************************/
package dsp.filter.polyphase;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,7 +54,7 @@ public class PolyphaseFIRInterpolatingFilter
public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation )
{
/* Ensure coefficients are an integral multiple of the interpolation factor */
assert( coefficients.length % interpolation == 0 );
Validate.isTrue(coefficients.length % interpolation == 0);
mTaps = coefficients;

View File

@@ -17,6 +17,7 @@
******************************************************************************/
package dsp.gain;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,7 +50,7 @@ public class DirectGainControl implements GainController, RealSampleListener
*/
public DirectGainControl( float gain, float minimum, float maximum, float increment )
{
assert( minimum > 0 );
Validate.isTrue(minimum > 0);
mDefaultGain = gain;
mGain = gain;

View File

@@ -0,0 +1,86 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 edac.trellis;
public class DMRNode extends Node
{
//Hamming error values indicating the number of bits set in each indexed value, 0-15
public static final int[] HAMMING_ERROR_COUNT = new int[]{0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
/**
* Transmitted quad-bit value, derived from ETSI TS 102-361-1, Table B.7 Trellis encoder state transition table
*
* Note: the B.7 table contains constellation point ID numbers that have to be converted into actual bit values
* where the bits represent the dibit pair for the constellation point.
*/
public static final int[][] DMR_TRANSITION_MATRIX = new int[][]
{
{2,13,14,1,7,8,11,4},
{14,1,7,8,11,4,2,13},
{10,5,6,9,15,0,3,12},
{6,9,15,0,3,12,10,5},
{15,0,3,12,10,5,6,9},
{3,12,10,5,6,9,15,0},
{7,8,11,4,2,13,14,1},
{11,4,2,13,14,1,7,8}
};
/**
* Trellis Coded Modulation (TCM) 3/4-rate trellis node for decoding DMR
*
* @param inputValue for the trellis time instant
* @param transmittedOutputValue the actual four-bit output value that was transmitted.
*/
public DMRNode(int inputValue, int transmittedOutputValue)
{
super(inputValue, transmittedOutputValue);
}
/**
* Calculates the error between the preceeding node and this node as the Hamming distance between the expected
* four-bit output value versus the actual transmitted output value.
*
* @param precedingNode that precedes this node
* @return error value for this node relative to the preceeding node argument
*/
@Override
public int getError(Node precedingNode)
{
if(precedingNode != null)
{
int expectedOutputValue = getOutputValue(precedingNode);
int errorMask = expectedOutputValue ^ getTransmittedOutputValue();
return HAMMING_ERROR_COUNT[errorMask];
}
return 0;
}
/**
* Expected output four-bit value when this node is preceeded by the argument node.
*
* @param precedingNode that precedes this node
* @return expected output value
*/
@Override
public int getOutputValue(Node precedingNode)
{
return DMR_TRANSITION_MATRIX[precedingNode.getInputValue()][getInputValue()];
}
}

View File

@@ -0,0 +1,77 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 edac.trellis;
public abstract class Node
{
private int mInputValue;
private int mTransmittedOutputValue;
/**
* Trellis Coded Modulation (TCM) node. A node represents a time instant on the trellis and contains an input
* value and the corresponding transmitted output value for the time instant. A node does not maintain the
* state at the time instant -- the state is determined by the input value of a preceeding node.
*
* @param inputValue for this node
* @param transmittedOutputValue for this node
*/
public Node(int inputValue, int transmittedOutputValue)
{
mInputValue = inputValue;
mTransmittedOutputValue = transmittedOutputValue;
}
/**
* Input value for this node.
*
* @return input value
*/
public int getInputValue()
{
return mInputValue;
}
/**
* Transmitted output value for this node
* @return transmitted output value
*/
public int getTransmittedOutputValue()
{
return mTransmittedOutputValue;
}
/**
* Calculates the error value for this node (time instant) by determining the expected output value that would have
* been transmitted using the state from the preceding node argument and this node's input value and comparing that
* expected value to the actual transmitted output value. This error is normally represented as the Hamming
* distance between the two values.
*
* @param precedingNode that precedes this node
* @return error value for this node relative to the preceeding node
*/
public abstract int getError(Node precedingNode);
/**
* Expected output value when this node is preceeded by the node specified in the argument.
*
* @param precedingNode that precedes this node
* @return expected output value
*/
public abstract int getOutputValue(Node precedingNode);
}

131
src/edac/trellis/Path.java Normal file
View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 edac.trellis;
import java.util.ArrayList;
import java.util.List;
public class Path
{
private List<Node> mNodes = new ArrayList<>();
private int mError = 0;
/**
* Trellis Coded Modulation (TCM) path. Represents a sequence of nodes that traverse various time instants and
* states across a trellis.
*
* Note: each node can be shared across multiple paths. This class does not assume that any nodes are unique.
* @param startingNode to start the path. This node normally contains an input value of zero, but is implementation
* specific.
*/
public Path(Node startingNode)
{
add(startingNode);
}
/**
* Trellis Coded Modulation (TCM) path. Represents a sequence of nodes that traverse various time instants and
* states across a trellis.
*
* Note: each node can be shared across multiple paths. This class does not assume that any nodes are unique.
*
* @param original path to create a copy from. Each of the nodes in the original will be added to this path and
* after construction both this path and the original will contain the exact same set of nodes.
*/
private Path(Path original)
{
for(Node node: original.getNodes())
{
add(node);
}
}
/**
* Cumulative error across all nodes contained within this path. Error value is updated as each new node is added
* to this path.
*
* @return cumulative error value
*/
public int getError()
{
return mError;
}
/**
* Length or number of nodes contained within this path. Note: this value includes the starting node and may
* also include a flushing node. The values contained in the starting and flushing nodes are normally not used
* when decoding a path.
*
* @return number of nodes in this path
*/
public int getLength()
{
return mNodes.size() - 1;
}
/**
* The set of nodes contained in this path.
* @return nodes
*/
public List<Node> getNodes()
{
return mNodes;
}
/**
* Adds the node to this path and updates the cumulative error value for this path by adding the node's error
* value to the running error total.
*
* @param node to add to this path
*/
public void add(Node node)
{
if(!mNodes.isEmpty())
{
mError += node.getError(getLastNode());
}
mNodes.add(node);
}
/**
* Last node in this path.
*
* @return last node
*/
public Node getLastNode()
{
if(!mNodes.isEmpty())
{
return mNodes.get(mNodes.size() - 1);
}
return null;
}
/**
* Utility method to create an exact copy of this path.
*
* @return a new copy of this path.
*/
public Path copyOf()
{
return new Path(this);
}
}

View File

@@ -0,0 +1,226 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 edac.trellis;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public abstract class ViterbiDecoder
{
private int mInputBitLength;
private int mInputValueCount;
private int mOutputBitLength;
private int mOutputValueCount;
/**
* Viterbi decoder for trellis coded modulation (TCM) encoded binary sequences.
*
* @param inputBitLength for coding (e.g. 3/4 rate input bit length is 3)
* @param outputBitLength for coding (e.g. 3/4 rate output bit length is 4)
*/
public ViterbiDecoder(int inputBitLength, int outputBitLength)
{
mInputBitLength = inputBitLength;
mOutputBitLength = outputBitLength;
mInputValueCount = (int)Math.pow(2.0, mInputBitLength);
mOutputValueCount = (int)Math.pow(2.0, mOutputBitLength);
}
/**
* Creates a node that represents an input value and the actual/transmitted output value.
*
* Note: this method must be implemented by a sub-class since the node requires a specific state transition
* table/matrix implementation.
*
* @param inputValue for the specific trellis node
* @param transmittedOutputValue the actual transmitted output value
* @return node for decoding
*/
protected abstract Node createNode(int inputValue, int transmittedOutputValue);
/**
* Creates a flushing or final node that represents an actual/transmitted output value and a flushing input value.
*
* The final flushing input value is implementation-specific, therefore this will be implemented in any subclass
* of this decoder. A normal flushing input value is zero.
*
* @param transmittedOutputValue the actual transmitted output value
* @return flushing node
*/
protected abstract Node createFlushingNode(int transmittedOutputValue);
/**
* Creates a starting node that represents a starting state and notional transmitted output value.
*
* The starting input value is implementation-specific, therefore this will be implemented in any subclass
* of this decoder. A normal starting input value is zero.
*
* @return starting node
*/
protected abstract Node createStartingNode();
/**
* Size in bits of the input and state values (e.g. 3/4 rate input size is 3)
*
* @return input bit size
*/
public int getInputBitLength()
{
return mInputBitLength;
}
/**
* Count of input or state values where the set of values is in the range of 0 <> count - 1
*
* @return input or state values count
*/
public int getInputValueCount()
{
return mInputValueCount;
}
/**
* Size in bits of the output constellation or transmitted symbol value (e.g. 3/4 rate output size is 4)
*
* @return output size in bits
*/
public int getOutputBitLength()
{
return mOutputBitLength;
}
/**
* Count of input or state values where the set of values is in the range of 0 <> count - 1
*
* @return input or state values count
*/
public int getOutputValueCount()
{
return mOutputValueCount;
}
/**
* Decodes the TCM encoded transmitted output values and returns a path the represents the most likely transmitted
* sequence of nodes.
*
* @param transmittedOutputValues from the encoded message
* @return most likely path representing the transmitted values
*/
public Path decode(int[] transmittedOutputValues)
{
Collection<Path> survivingPaths = new ArrayList<>();
survivingPaths.add(new Path(createStartingNode()));
//Add all but the last transmitted value to the starting path, creating a set of survivors at each iteration.
for(int x = 0; x < transmittedOutputValues.length - 1; x++)
{
survivingPaths = add(survivingPaths, transmittedOutputValues[x]);
}
//Flush the survivors with the final transmitted value and return the lone surviving path
return flush(survivingPaths, transmittedOutputValues[transmittedOutputValues.length - 1]);
}
/**
* Creates a set of new nodes representing all possible input values and the transmitted output value argument
* and iteratively adds the set of nodes to a copy of each of the surviving paths. A set of surviving paths is
* returned that represents each path that terminates at a distinct input/state value that has the least error
* value.
*
* @param survivingPaths from the previous time instant to evaluate for the next time instant in the trellis
* using the transmitted output value.
*
* @param transmittedOutputValue that will be decoded for this time instant.
*
* @return paths that have survived (ie have the lowest error value) after adding nodes for the transmitted
* output value
*
*/
public Collection<Path> add(Collection<Path> survivingPaths, int transmittedOutputValue)
{
Map<Integer,Path> survivingPathMap = new HashMap<>();
for(int x = 0; x < getInputValueCount(); x++)
{
//Create a single/unique node for each possible input value combined with the transmitted output value
Node node = createNode(x, transmittedOutputValue);
//Evaluate each surviving path by creating a copy and adding the node to it and see if it survives
for(Path path: survivingPaths)
{
Path pathToEvaluate = path.copyOf();
pathToEvaluate.add(node);
if(survivingPathMap.containsKey(node.getInputValue()))
{
Path survivingPath = survivingPathMap.get(node.getInputValue());
//Replace the current survivor path if this evaluate path has a lower error value
if(pathToEvaluate.getError() < survivingPath.getError())
{
survivingPathMap.put(node.getInputValue(), pathToEvaluate);
}
}
else
{
//No survivor yet -- set this evaluate path as the first survivor
survivingPathMap.put(node.getInputValue(), pathToEvaluate);
}
}
}
return survivingPathMap.values();
}
/**
* Flushes each of the surviving paths with a final/flushing node using the transmitted symbol. A final flushing
* input value or state is implementation-specific, but normally uses a value of zero. Since all paths will be
* evaluated against each other as terminating at the same state, only the path with the lowest error value will
* be returned from this method.
*
* @param survivingPaths to flush with the final flushing node
* @param transmittedOutputValue to use for the final flushing node
*
* @return the single survivor path that remains after adding the flushing node to each of the candidate argument
* survivor paths.
*/
public Path flush(Collection<Path> survivingPaths, int transmittedOutputValue)
{
Node flushingNode = createFlushingNode(transmittedOutputValue);
Path bestPath = null;
for(Path survivingPath: survivingPaths)
{
survivingPath.add(flushingNode);
if(bestPath == null || (survivingPath.getError() < bestPath.getError()))
{
bestPath = survivingPath;
}
}
return bestPath;
}
}

View File

@@ -0,0 +1,159 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 edac.trellis;
import bits.BinaryMessage;
import bits.CorrectedBinaryMessage;
import java.util.List;
public class ViterbiDecoder_3_4_DMR extends ViterbiDecoder
{
/**
* Viterbi decoder for Digital Mobile Radio (DMR) 3/4 rate Trellis Coded Modulation (TCM) encoded messages.
*/
public ViterbiDecoder_3_4_DMR()
{
super(3,4);
}
/**
* Decodes a 3/4 rate trellis coded modulation (TCM) encoded DMR binary message containing 196 bits that have
* already been deinterleaved.
*
* @param encodedMessage to decode that has already been deinterleaved.
* @return decoded message
*/
public BinaryMessage decode(BinaryMessage encodedMessage)
{
int[] symbols = getSymbols(encodedMessage);
Path mostLikelyPath = decode(symbols);
return getMessage(mostLikelyPath);
}
/**
* Extracts the decoded/corrected message from the surviving path argument.
*
* @param path to extract a message from
* @return corrected binary message.
*/
private static CorrectedBinaryMessage getMessage(Path path)
{
List<Node> mNodes = path.getNodes();
//Each node contains an input value excluding the starting(0) and final flushing(0) nodes.
CorrectedBinaryMessage message = new CorrectedBinaryMessage((mNodes.size() - 2) * 3);
if(mNodes.size() > 2)
{
for(int x = 1; x < mNodes.size() - 1; x++)
{
int inputValue = mNodes.get(x).getInputValue();
int messageOffset = 3 * (x - 1);
if((inputValue & 4) == 4)
{
message.set(messageOffset);
}
if((inputValue & 2) == 2)
{
message.set(messageOffset + 1);
}
if((inputValue & 1) == 1)
{
message.set(messageOffset + 2);
}
}
}
//Transfer the corrected error count to the message
message.setCorrectedBitCount(path.getError());
return message;
}
/**
* Extracts (49) four-bit symbols (196 bits / 4) from the deinterleaved DMR binary message
*
* @param encodedMessage that deinterleaved and contains 196-bits of 3/4 TCM encoding
* @return symbols
*/
public int[] getSymbols(BinaryMessage encodedMessage)
{
//Ensure we have an integral number of transmitted symbols (nibbles) in the message
if(encodedMessage.size() % getOutputBitLength() != 0)
{
throw new IllegalArgumentException("Encoded message must contain an integral number of 4-bit symbols -" +
" message size: " + encodedMessage.size());
}
int[] symbols = new int[encodedMessage.size() / getOutputBitLength()];
int index = 0;
int pointer = 0;
while(index < encodedMessage.size())
{
int symbol = encodedMessage.getInt(index, index + (getOutputBitLength() - 1));
symbols[pointer++] = symbol;
index += getOutputBitLength();
}
return symbols;
}
/**
* Creates a DMR node
* @param inputValue for the specific trellis node
* @param transmittedOutputValue the actual transmitted output value
* @return created node
*/
@Override
protected Node createNode(int inputValue, int transmittedOutputValue)
{
return new DMRNode(inputValue, transmittedOutputValue);
}
/**
* Creates a DMR flushing node. DMR uses an input value of zero to flush the final output value out of the
* encoder.
*
* @param transmittedOutputValue the actual transmitted output value
* @return
*/
@Override
protected Node createFlushingNode(int transmittedOutputValue)
{
return new DMRNode(0, transmittedOutputValue);
}
/**
* Creates a DMR starting node. DMR uses a starting input value of zero for the initial node.
*/
@Override
protected Node createStartingNode()
{
return new DMRNode(0,0);
}
}

View File

@@ -151,41 +151,4 @@ public class RadioReferenceService
return mClient.getUserFeedBroadcasts(mUserAuthorization);
}
public static void main(String[] args)
{
try
{
RadioReferenceService service = new RadioReferenceService("dsheirer", "1dodgeram");
Long expiration = service.getAccountExpirationDate();
mLog.debug("Expiration Date:" + (expiration != null ? new Date(expiration).toString() : "unknown"));
mLog.debug("Is Active: " + service.isAccountActive());
UserFeedBroadcast[] feeds = service.getUserFeedBroadcasts();
for(UserFeedBroadcast feed: feeds)
{
mLog.debug("Feed ID: " + feed.getFeedId() + " Desc:" + feed.getDescr());
}
}
catch(AxisFault af)
{
mLog.debug("Hey, I caught an axis fault!");
mLog.debug("Reason:" + af.getFaultReason());
}
catch(RemoteException re)
{
re.printStackTrace();
}
catch(Exception e)
{
e.printStackTrace();
}
mLog.debug("Finished!");
}
}

View File

@@ -33,6 +33,7 @@ import icon.IconManager;
import map.MapService;
import module.log.EventLogManager;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import playlist.PlaylistManager;
@@ -80,16 +81,16 @@ public class SDRTrunk implements Listener<TunerEvent>
public SDRTrunk()
{
mLog.info("");
mLog.info("*******************************************************************");
mLog.info("**** sdrtrunk: a trunked radio and digital decoding application ***");
mLog.info("**** website: https://github.com/dsheirer/sdrtrunk ***");
mLog.info("*******************************************************************");
mLog.info("");
mLog.info("Host CPU Cores: " + Runtime.getRuntime().availableProcessors());
mLog.info("Host Memory Total: " + Runtime.getRuntime().totalMemory());
mLog.info("Host Memory Max: " + Runtime.getRuntime().maxMemory());
mLog.info("Host Memory Free: " + Runtime.getRuntime().freeMemory());
mLog.info("Memory Logging Format: [Used/Allocated PercentUsed%]");
mLog.info("Host CPU Cores: " + Runtime.getRuntime().availableProcessors());
mLog.info("Host OS Name: " + System.getProperty("os.name"));
mLog.info("Host OS Arch: " + System.getProperty("os.arch"));
mLog.info("Host OS Version: " + System.getProperty("os.version"));
mLog.info("Host Max Java Memory: " + FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
//Setup the application home directory
Path home = getHomePath();

View File

@@ -1,65 +1,82 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 instrument;
import controller.channel.Channel.ChannelType;
import instrument.tap.Tap;
import instrument.tap.TapGroup;
import module.Module;
import module.ProcessingChain;
import source.Source;
import java.util.ArrayList;
import java.util.List;
import module.Module;
import module.ProcessingChain;
import source.Source;
import controller.channel.Channel.ChannelType;
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
{
public InstrumentableProcessingChain()
{
super( "instrumented", ChannelType.STANDARD );
}
public InstrumentableProcessingChain()
{
super(ChannelType.STANDARD);
}
public void setSource( Source source ) throws IllegalStateException
{
mSource = source;
}
public void setSource(Source source) throws IllegalStateException
{
mSource = source;
}
@Override
public List<TapGroup> getTapGroups()
{
List<TapGroup> groups = new ArrayList<>();
for( Module module: getModules() )
{
if( module instanceof Instrumentable )
{
groups.addAll( ((Instrumentable)module).getTapGroups() );
}
}
return groups;
}
@Override
public List<TapGroup> getTapGroups()
{
List<TapGroup> groups = new ArrayList<>();
@Override
public void registerTap( Tap tap )
{
for( Module module: getModules() )
{
if( module instanceof Instrumentable )
{
((Instrumentable)module).registerTap( tap );
}
}
}
for(Module module : getModules())
{
if(module instanceof Instrumentable)
{
groups.addAll(((Instrumentable)module).getTapGroups());
}
}
@Override
public void unregisterTap( Tap tap )
{
for( Module module: getModules() )
{
if( module instanceof Instrumentable )
{
((Instrumentable)module).unregisterTap( tap );
}
}
}
return groups;
}
@Override
public void registerTap(Tap tap)
{
for(Module module : getModules())
{
if(module instanceof Instrumentable)
{
((Instrumentable)module).registerTap(tap);
}
}
}
@Override
public void unregisterTap(Tap tap)
{
for(Module module : getModules())
{
if(module instanceof Instrumentable)
{
((Instrumentable)module).unregisterTap(tap);
}
}
}
}

View File

@@ -24,16 +24,17 @@ import audio.IAudioPacketProvider;
import audio.squelch.ISquelchStateListener;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
import channel.heartbeat.Heartbeat;
import channel.heartbeat.IHeartbeatListener;
import channel.heartbeat.IHeartbeatProvider;
import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestListener;
import channel.metadata.IAttributeChangeRequestProvider;
import channel.state.ChannelState;
import channel.state.DecoderState;
import channel.state.DecoderStateEvent;
import channel.state.DecoderStateEvent.Event;
import channel.state.IDecoderStateEventListener;
import channel.state.IDecoderStateEventProvider;
import channel.state.State;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener;
@@ -63,8 +64,6 @@ import sample.real.RealBuffer;
import source.ComplexSource;
import source.RealSource;
import source.Source;
import source.SourceException;
import source.tuner.TunerChannelSource;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProvider;
@@ -103,12 +102,12 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private Broadcaster<DecoderStateEvent> mDecoderStateEventBroadcaster = new Broadcaster<>();
private Broadcaster<FrequencyChangeEvent> mFrequencyChangeEventBroadcaster = new Broadcaster<>();
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
private Broadcaster<Message> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
private String mName;
private AtomicBoolean mRunning = new AtomicBoolean();
protected Source mSource;
@@ -120,13 +119,10 @@ public class ProcessingChain implements IChannelEventListener
/**
* Creates a processing chain for managing a set of modules
*
* @param name for thread pool naming each thread
* @param channelType
*/
public ProcessingChain(String name, ChannelType channelType)
public ProcessingChain(ChannelType channelType)
{
mName = name;
mChannelState = new ChannelState(channelType);
addModule(mChannelState);
@@ -211,6 +207,8 @@ public class ProcessingChain implements IChannelEventListener
}
mSource = source;
addModule(mSource);
}
/**
@@ -322,7 +320,18 @@ public class ProcessingChain implements IChannelEventListener
if(module instanceof IFrequencyChangeListener)
{
mFrequencyChangeEventBroadcaster.addListener(((IFrequencyChangeListener) module).getFrequencyChangeListener());
Listener<FrequencyChangeEvent> frequencyChangeEventListener =
((IFrequencyChangeListener)module).getFrequencyChangeListener();
if(frequencyChangeEventListener != null)
{
mFrequencyChangeEventBroadcaster.addListener(frequencyChangeEventListener);
}
}
if(module instanceof IHeartbeatListener)
{
mHeartbeatBroadcaster.addListener(((IHeartbeatListener)module).getHeartbeatListener());
}
if(module instanceof IMessageListener)
@@ -387,6 +396,11 @@ public class ProcessingChain implements IChannelEventListener
mFrequencyChangeEventBroadcaster.removeListener(((IFrequencyChangeListener) module).getFrequencyChangeListener());
}
if(module instanceof IHeartbeatListener)
{
mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener());
}
if(module instanceof IMessageListener)
{
mMessageBroadcaster.removeListener(((IMessageListener) module).getMessageListener());
@@ -444,6 +458,11 @@ public class ProcessingChain implements IChannelEventListener
((IFrequencyChangeProvider) module).setFrequencyChangeListener(mFrequencyChangeEventBroadcaster);
}
if(module instanceof IHeartbeatProvider)
{
((IHeartbeatProvider)module).setHeartbeatListener(mHeartbeatBroadcaster);
}
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
@@ -501,6 +520,11 @@ public class ProcessingChain implements IChannelEventListener
((IFrequencyChangeProvider) module).setFrequencyChangeListener(null);
}
if(module instanceof IHeartbeatProvider)
{
((IHeartbeatProvider)module).removeHeartbeatListener();
}
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(null);
@@ -538,6 +562,9 @@ public class ProcessingChain implements IChannelEventListener
module.reset();
}
//Setup the channel state to monitor source overflow conditions
mSource.setOverflowListener(mChannelState);
/* Register with the source to receive sample data. Setup a
* timer task to process the buffer queues 50 times a second
* (every 20 ms) */
@@ -554,25 +581,6 @@ public class ProcessingChain implements IChannelEventListener
+ "sample type - cannot start processing chain");
}
/* If this is a tuner source, broadcast the frequency to all
* of the decoder state's and start the samples flowing */
if(mSource instanceof TunerChannelSource)
{
TunerChannelSource tcs = (TunerChannelSource) mSource;
try
{
long frequency = tcs.getFrequency();
mDecoderStateEventBroadcaster.broadcast(
new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, State.IDLE, frequency));
}
catch(SourceException e)
{
mLog.error("Error getting frequency from tuner channel source", e);
}
}
/* Start each of the modules */
for(Module module : mModules)
{
@@ -585,8 +593,6 @@ public class ProcessingChain implements IChannelEventListener
mLog.error("Error starting module", e);
}
}
mSource.start(ThreadPool.SCHEDULED);
}
else
{
@@ -603,9 +609,17 @@ public class ProcessingChain implements IChannelEventListener
{
if(mRunning.compareAndSet(true, false))
{
/* Stop each of the modules */
for(Module module : mModules)
{
module.stop();
}
if(mSource != null)
{
mSource.stop();
removeModule(mSource);
mSource.setOverflowListener(null);
switch(mSource.getSampleType())
{
@@ -625,11 +639,6 @@ public class ProcessingChain implements IChannelEventListener
mSource = null;
}
/* Stop each of the modules */
for(Module module : mModules)
{
module.stop();
}
}
}
@@ -756,6 +765,24 @@ public class ProcessingChain implements IChannelEventListener
mMessageBroadcaster.removeListener(listener);
}
/**
* Adds the listener to receive frequency change events from the processing chain
* @param listener to receive events
*/
public void addFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mFrequencyChangeEventBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving frequency change events
* @param listener to remove
*/
public void removeFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mFrequencyChangeEventBroadcaster.removeListener(listener);
}
/**
* Adds the listener to receive call events from all modules.
*/

View File

@@ -103,35 +103,35 @@ public class DecoderFactory
//Low-pass filter with ~60 dB attenuation and 89 taps
private static final FIRFilterSpecification MPT1327_FILTER_SPECIFICATION = FIRFilterSpecification.lowPassBuilder()
.sampleRate( 48000 )
.gridDensity( 16 )
.passBandCutoff( 3200 )
.passBandAmplitude( 1.0 )
.passBandRipple( 0.02 )
.stopBandStart( 4000 )
.stopBandAmplitude( 0.0 )
.stopBandRipple( 0.03 )
.build();
.sampleRate( 48000 ).gridDensity( 16 ).passBandCutoff( 3200 ).passBandAmplitude( 1.0 ).passBandRipple( 0.02 )
.stopBandStart( 4000 ).stopBandAmplitude( 0.0 ).stopBandRipple( 0.03 ).build();
private static final FIRFilterSpecification P25_C4FM_IQ_SPECIFICATION = FIRFilterSpecification.lowPassBuilder()
.sampleRate(48000).gridDensity(16).passBandCutoff(6750).passBandAmplitude(1.0).passBandRipple(0.01)
.stopBandStart(7000).stopBandAmplitude(0.0).stopBandRipple(0.008).build();
private static final FIRFilterSpecification P25_C4FM_DEMOD_SPECIFICATION = FIRFilterSpecification.lowPassBuilder()
.sampleRate(48000).gridDensity(16).passBandCutoff(2500).passBandAmplitude(1.0).passBandRipple(0.01)
.stopBandStart(4000).stopBandAmplitude(0.0).stopBandRipple(0.008).build();
private static float[] MPT1327_LOWPASS_FILTER;
private static float[] P25_C4FM_IQ_FILTER;
private static float[] P25_C4FM_DEMOD_FILTER;
static
{
try
{
MPT1327_LOWPASS_FILTER = FilterFactory.getTaps(MPT1327_FILTER_SPECIFICATION);
P25_C4FM_IQ_FILTER = FilterFactory.getTaps(P25_C4FM_IQ_SPECIFICATION);
P25_C4FM_DEMOD_FILTER = FilterFactory.getTaps(P25_C4FM_DEMOD_SPECIFICATION);
}
catch(Exception e)
{
mLog.error("Couldn't design MPT1327 bandpass filter");
mLog.error("Couldn't design startup filter(s)");
}
}
public static final boolean NO_REMOVE_DC = false;
public static final boolean REMOVE_DC = true;
public static final int NO_FREQUENCY_CORRECTION = 0;
/**
* Returns a list of one primary decoder and any auxiliary decoders, as
* specified in the configurations.
@@ -222,15 +222,13 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD)
{
long timeout = mptConfig.getCallTimeout() * 1000; //convert to milliseconds
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
modules.add(new TrafficChannelManager(channelModel, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), timeout, mptConfig.getTrafficChannelPoolSize()));
(aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize()));
}
modules.add(new FMDemodulatorModule(iqPass, iqStop));
modules.add(new DemodulatedAudioFilterModule(MPT1327_LOWPASS_FILTER, 1.0f));
modules.add(new DemodulatedAudioFilterModule(P25_C4FM_DEMOD_FILTER, 1.0f));
modules.add(new AudioModule(metadata));
break;
case PASSPORT:
@@ -249,7 +247,8 @@ public class DecoderFactory
switch(modulation)
{
case C4FM:
modules.add(new FMDemodulatorModule(6750, 7500));
modules.add(new FMDemodulatorModule(P25_C4FM_IQ_FILTER));
modules.add(new DemodulatedAudioFilterModule(P25_C4FM_DEMOD_FILTER, 1.0f));
modules.add(new P25_C4FMDecoder(aliasList, decodeConfig.getAFCMaximumCorrection()));
modules.add(new P25DecoderState(aliasList, channelType, Modulation.C4FM,
p25Config.getIgnoreDataCalls()));
@@ -264,12 +263,9 @@ public class DecoderFactory
if(channelType == ChannelType.STANDARD)
{
//Set call timeout to 3 seconds for P25 traffic channels
long timeout = 3000;
modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig,
modules.add(new TrafficChannelManager(channelModel, decodeConfig,
channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(),
(aliasList != null ? aliasList.getName() : null), timeout, p25Config.getTrafficChannelPoolSize()));
(aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize()));
}
modules.add(new P25AudioModule(metadata));

View File

@@ -54,7 +54,7 @@ public class LJ1200DecoderState extends DecoderState
super(aliasList);
mAddressAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO,
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.LOJACK);
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE);
}
@Override

View File

@@ -389,9 +389,9 @@ public class MPT1327DecoderState extends DecoderState
{
StringBuilder sb = new StringBuilder();
sb.append("Activity Summary\n");
sb.append("Decoder:\tMPT-1327\n");
sb.append("Site:\t");
sb.append("Activity Summary - Decoder: MPT-1327\n");
sb.append(DIVIDER1);
sb.append("Site: ");
if(mSite != null)
{
@@ -403,9 +403,7 @@ public class MPT1327DecoderState extends DecoderState
if(siteAlias != null)
{
sb.append(" ");
sb.append(siteAlias.getName());
sb.append("\n");
sb.append(" ").append(siteAlias.getName()).append("\n");
}
}
}
@@ -414,13 +412,11 @@ public class MPT1327DecoderState extends DecoderState
sb.append("Unknown\n");
}
sb.append("\n\n");
sb.append("Talkgroups\n\n");
sb.append(DIVIDER2).append("Talkgroups: ");
if(mGroups.isEmpty())
{
sb.append(" None\n");
sb.append("None\n");
}
else
{
@@ -429,8 +425,7 @@ public class MPT1327DecoderState extends DecoderState
for(String talkgroup : talkgroups)
{
sb.append(" ");
sb.append(talkgroup);
sb.append("\n ").append(talkgroup);
if(hasAliasList())
{
@@ -450,7 +445,7 @@ public class MPT1327DecoderState extends DecoderState
for(String member : members)
{
sb.append(" >");
sb.append(" >");
sb.append(member);
if(hasAliasList())
@@ -466,27 +461,26 @@ public class MPT1327DecoderState extends DecoderState
sb.append("\n");
}
sb.append("\n");
}
}
sb.append("\n");
sb.append(DIVIDER2).append("All Idents: ");
sb.append("Individual Idents\n");
if(mIdents.isEmpty())
{
sb.append(" None\n");
sb.append("None\n");
}
else
{
sb.append("\n");
Iterator<String> it = mIdents.iterator();
while(it.hasNext())
{
String ident = it.next();
sb.append(" ");
sb.append("");
sb.append(ident);
sb.append(" ");

View File

@@ -9,21 +9,21 @@ import instrument.tap.stream.FloatTap;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.real.RealBuffer;
import sample.real.RealSampleListener;
import sample.real.RealSampleProvider;
import source.tuner.TunerChannelSource;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.FrequencyChangeEvent.Event;
import source.tuner.frequency.IFrequencyChangeListener;
import dsp.gain.DirectGainControl;
public class C4FMSymbolFilter implements Listener<RealBuffer>,
IFrequencyChangeListener,
RealSampleProvider,
Instrumentable
public class C4FMSymbolFilter implements Listener<RealBuffer>, IFrequencyChangeListener, RealSampleProvider, Instrumentable
{
private static final float TAPS[][] =
private static final float TAPS[][] =
{
{ 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128
{ -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128
@@ -198,8 +198,7 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
private RealSampleListener mListener;
private DirectGainControl mGainController =
new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f );
private int mFrequencyAdjustmentRequested = 0;
private int mFrequencyCorrection = 0;
@@ -258,9 +257,8 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
correction = -mFrequencyCorrectionMaximum;
}
broadcast( new FrequencyChangeEvent(
Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) );
mFrequencyAdjustmentRequested = 0;
}
}
@@ -452,6 +450,10 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
}
}
/**
* Broadcasts a frequency change event to the registered listener
* @param event
*/
public void broadcast( FrequencyChangeEvent event )
{
if( mFrequencyChangeListener != null )
@@ -459,7 +461,11 @@ public class C4FMSymbolFilter implements Listener<RealBuffer>,
mFrequencyChangeListener.receive( event );
}
}
/**
* Sets the listener for frequency change events.
* @param listener
*/
public void setFrequencyChangeListener( Listener<FrequencyChangeEvent> listener )
{
mFrequencyChangeListener = listener;

View File

@@ -4,8 +4,10 @@ import alias.AliasList;
import instrument.Instrumentable;
import module.decode.Decoder;
import module.decode.DecoderType;
import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProvider;
public abstract class P25Decoder extends Decoder implements Instrumentable
public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable
{
private P25MessageProcessor mMessageProcessor;

View File

@@ -19,9 +19,7 @@ package module.decode.p25;
import alias.Alias;
import alias.AliasList;
import alias.PatchGroupAlias;
import alias.id.AliasIDType;
import alias.id.talkgroup.TalkgroupID;
import channel.metadata.AliasedStringAttributeMonitor;
import channel.metadata.Attribute;
import channel.metadata.AttributeChangeRequest;
@@ -150,6 +148,7 @@ public class P25DecoderState extends DecoderState
private static final DecimalFormat mFrequencyFormatter =
new DecimalFormat("0.000000");
private module.decode.p25.message.tsbk.osp.control.NetworkStatusBroadcast mNetworkStatus;
private NetworkStatusBroadcastExtended mNetworkStatusExtended;
private ProtectionParameterBroadcast mProtectionParameterBroadcast;
@@ -163,7 +162,6 @@ public class P25DecoderState extends DecoderState
private Map<Integer,IdentifierUpdate> mBands = new HashMap<>();
private Map<String,Long> mRegistrations = new HashMap<>();
private Map<String,IAdjacentSite> mNeighborMap = new HashMap<>();
private Map<String,List<String>> mPatchGroupMap = new HashMap<>();
private String mLastCommandEventID;
private String mLastPageEventID;
@@ -188,6 +186,7 @@ public class P25DecoderState extends DecoderState
private P25CallEvent mCurrentCallEvent;
private List<String> mCallDetectTalkgroups = new ArrayList<>();
private Map<String,P25CallEvent> mChannelCallMap = new HashMap<>();
private PatchGroupManager mPatchGroupManager;
public P25DecoderState(AliasList aliasList,
ChannelType channelType,
@@ -199,6 +198,7 @@ public class P25DecoderState extends DecoderState
mModulation = modulation;
mIgnoreDataCalls = ignoreDataCalls;
mPatchGroupManager = new PatchGroupManager(aliasList, getCallEventBroadcaster());
mSiteAttributeMonitor = new AliasedStringAttributeMonitor(Attribute.NETWORK_ID_2,
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE);
mFromTalkgroupMonitor = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM,
@@ -315,6 +315,8 @@ public class P25DecoderState extends DecoderState
*/
public void registerCallEvent(P25CallEvent event)
{
String channel = event.getChannel();
if(mChannelCallMap.containsKey(event.getChannel()))
{
P25CallEvent previousEvent = mChannelCallMap.remove(event.getChannel());
@@ -419,7 +421,7 @@ public class P25DecoderState extends DecoderState
mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details((hdu.isEncrypted() ? "ENCRYPTED " : ""))
.details((hdu.isEncryptedAudio() ? "ENCRYPTED " : ""))
.frequency(mCurrentChannelFrequency)
.to(to)
.build();
@@ -1105,7 +1107,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList())
.channel(gvcu.getChannelA())
.details((gvcu.isEncrypted() ? "ENCRYPTED" : ""))
.details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcu.getDownlinkFrequencyA())
.to(userA)
.build());
@@ -1120,7 +1122,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList())
.channel(gvcu.getChannelB())
.details((gvcu.isEncrypted() ? "ENCRYPTED" : ""))
.details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcu.getDownlinkFrequencyB())
.to(userB)
.build());
@@ -1149,7 +1151,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList())
.channel(gvcue.getTransmitChannel())
.details((gvcue.isEncrypted() ? "ENCRYPTED" : ""))
.details((gvcue.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcue.getDownlinkFrequency())
.to(group)
.build());
@@ -1185,7 +1187,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null)
{
mCurrentCallEvent.setDetails(
(gvcuser.isEncrypted() ? "ENCRYPTED " : "") +
(gvcuser.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(gvcuser.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent);
@@ -1397,7 +1399,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null)
{
mCurrentCallEvent.setDetails(
(tivcu.isEncrypted() ? "ENCRYPTED " : "") +
(tivcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(tivcu.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent);
@@ -1480,7 +1482,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null)
{
mCurrentCallEvent.setDetails(
(uuvcu.isEncrypted() ? "ENCRYPTED " : "") +
(uuvcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(uuvcu.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent);
@@ -1557,6 +1559,7 @@ public class P25DecoderState extends DecoderState
break;
case IDENTIFIER_UPDATE_NON_VUHF:
case IDENTIFIER_UPDATE_VHF_UHF_BANDS:
case IDENTIFIER_UPDATE_TDMA:
IdentifierUpdate iu = (IdentifierUpdate)tsbk;
if(!mBands.containsKey(iu.getIdentifier()))
@@ -1710,33 +1713,41 @@ public class P25DecoderState extends DecoderState
.build());
break;
case SNDCP_RF_CONFIRMED_DATA:
SNDCPUserData sud = (SNDCPUserData)pduc;
StringBuilder sbFrom = new StringBuilder();
StringBuilder sbTo = new StringBuilder();
sbFrom.append(sud.getSourceIPAddress());
sbTo.append(sud.getDestinationIPAddress());
if(sud.getIPProtocol() == IPProtocol.UDP)
if(pduc instanceof SNDCPUserData)
{
sbFrom.append(":");
sbFrom.append(sud.getUDPSourcePort());
sbTo.append(":");
sbTo.append(sud.getUDPDestinationPort());
SNDCPUserData sud = (SNDCPUserData)pduc;
StringBuilder sbFrom = new StringBuilder();
StringBuilder sbTo = new StringBuilder();
sbFrom.append(sud.getSourceIPAddress());
sbTo.append(sud.getDestinationIPAddress());
if(sud.getIPProtocol() == IPProtocol.UDP)
{
sbFrom.append(":");
sbFrom.append(sud.getUDPSourcePort());
sbTo.append(":");
sbTo.append(sud.getUDPDestinationPort());
}
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details("DATA: " + sud.getPayload() +
" RADIO IP:" + sbTo.toString())
.frequency(mCurrentChannelFrequency)
.from(sbFrom.toString())
.to(pduc.getLogicalLinkID())
.build());
}
else
{
mLog.error("Error - expected SNDCPUserData instance but class was: " + pduc.getClass() +
" and PDU Type:" + pduc.getPDUType().name());
}
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details("DATA: " + sud.getPayload() +
" RADIO IP:" + sbTo.toString())
.frequency(mCurrentChannelFrequency)
.from(sbFrom.toString())
.to(pduc.getLogicalLinkID())
.build());
break;
case SNDCP_RF_UNCONFIRMED_DATA:
break;
@@ -2719,6 +2730,9 @@ public class P25DecoderState extends DecoderState
switch(((MotorolaTSBKMessage)tsbk).getMotorolaOpcode())
{
case PATCH_GROUP_CHANNEL_GRANT:
//Cleanup patch groups - auto-expire any patch groups before we allocate a channel
mPatchGroupManager.cleanupPatchGroups();
PatchGroupVoiceChannelGrant pgvcg = (PatchGroupVoiceChannelGrant)tsbk;
channel = pgvcg.getChannel();
@@ -2732,6 +2746,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(pgvcg.isTDMAChannel() ? "TDMA " : "");
details.append(pgvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(pgvcg.getPriority());
details.append(pgvcg.isEncryptedChannel() ? " ENCRYPTED" : "");
@@ -2750,10 +2765,15 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!pgvcg.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
case PATCH_GROUP_CHANNEL_GRANT_UPDATE:
//Cleanup patch groups - auto-expire any patch groups before we allocate a channel
mPatchGroupManager.cleanupPatchGroups();
PatchGroupVoiceChannelGrantUpdate gvcgu = (PatchGroupVoiceChannelGrantUpdate)tsbk;
channel = gvcgu.getChannel1();
@@ -2768,9 +2788,8 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
.aliasList(getAliasList())
.channel(channel)
.details((gvcgu.isEncrypted() ? "ENCRYPTED " : "") +
"PATCH UPDATE - GROUP 2:" + gvcgu.getPatchGroupAddress2() +
" DN:" + gvcgu.getDownlinkFrequency2())
.details((gvcgu.isTDMAChannel1() ? "TDMA " : "") +
(gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
.frequency(gvcgu.getDownlinkFrequency1())
.to(to)
.build();
@@ -2779,40 +2798,46 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!gvcgu.isTDMAChannel1())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
String channel2 = gvcgu.getChannel2();
String to2 = gvcgu.getPatchGroupAddress2();
if(hasCallEvent(channel2, null, to2))
{
updateCallEvent(channel2, null, to2);
}
else
{
P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL)
.aliasList(getAliasList())
.channel(channel2)
.details((gvcgu.isTDMAChannel2() ? "TDMA " : "") +
(gvcgu.isEncrypted() ? "ENCRYPTED " : ""))
.frequency(gvcgu.getDownlinkFrequency2())
.to(to2)
.build();
registerCallEvent(event);
broadcast(event);
}
if(!gvcgu.isTDMAChannel2())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel2)));
}
break;
case PATCH_GROUP_ADD:
PatchGroupAdd pga = (PatchGroupAdd)tsbk;
if(addPatchGroup(pga.getPatchGroupAddress(), pga.getPatchedTalkgroups()))
{
StringBuilder sb = new StringBuilder();
sb.append("PATCH GROUP:").append(pga.getPatchGroupAddress());
sb.append(" ").append(pga.getPatchedTalkgroups());
broadcast(new P25CallEvent.Builder(CallEventType.PATCH_GROUP_ADD)
.aliasList(getAliasList())
.details(sb.toString())
.build());
}
mPatchGroupManager.updatePatchGroup(pga.getPatchGroupAddress(), pga.getPatchedTalkgroups());
break;
case PATCH_GROUP_DELETE:
PatchGroupDelete pgd = (PatchGroupDelete)tsbk;
if(removePatchGroup(pgd.getPatchGroupAddress(), pgd.getPatchedTalkgroups()))
{
StringBuilder sbpgd = new StringBuilder();
sbpgd.append("PATCH GROUP:").append(pgd.getPatchGroupAddress());
sbpgd.append(" ").append(pgd.getPatchedTalkgroups());
broadcast(new P25CallEvent.Builder(CallEventType.PATCH_GROUP_DELETE)
.aliasList(getAliasList())
.details(sbpgd.toString())
.build());
}
mPatchGroupManager.removePatchGroup(pgd.getPatchGroupAddress());
break;
case CCH_PLANNED_SHUTDOWN:
if(!mControlChannelShutdownLogged)
@@ -2827,174 +2852,14 @@ public class P25DecoderState extends DecoderState
}
}
/**
* Creates a new patch group or updates an existing patch group with the patched talkgroup entries.
*
* @param patchGroupID for the patch group
* @param talkgroupsToAdd to include in the patch group
* @return true if the patch group or the patch group map was updated
*/
private boolean addPatchGroup(String patchGroupID, List<String> talkgroupsToAdd)
{
boolean updated = false;
if(patchGroupID != null && !talkgroupsToAdd.isEmpty())
{
List<String> patchedTalkgroups = mPatchGroupMap.get(patchGroupID);
if(patchedTalkgroups == null)
{
patchedTalkgroups = new ArrayList<>();
}
for(String talkgroupToAdd : talkgroupsToAdd)
{
//Exclude the patch group ID if it is included in the patched talkgroup list so that we don't have
//an infinite loop situation
if(talkgroupToAdd.equals(patchGroupID))
{
continue;
}
if(!patchedTalkgroups.contains(talkgroupToAdd))
{
patchedTalkgroups.add(talkgroupToAdd);
updated = true;
}
}
if(updated)
{
mPatchGroupMap.put(patchGroupID, patchedTalkgroups);
updatePatchAlias(patchGroupID, patchedTalkgroups);
}
}
return updated;
}
/**
* Removes the patched talkgroups from the patch group and removes the patch group from the patch group map if
* there are no more patched talkgroups in the patch group.
*
* @param patchGroupID for the patch group
* @param talkgroupsToRemove to remove from the patch group
* @return true if the patch group or the patch group map was updated
*/
private boolean removePatchGroup(String patchGroupID, List<String> talkgroupsToRemove)
{
boolean updated = false;
if(patchGroupID != null && !talkgroupsToRemove.isEmpty())
{
List<String> patchedTalkgroups = mPatchGroupMap.get(patchGroupID);
if(patchedTalkgroups != null)
{
for(String talkgroupToRemove : talkgroupsToRemove)
{
if(patchedTalkgroups.contains(talkgroupToRemove))
{
patchedTalkgroups.remove(talkgroupToRemove);
updated = true;
}
}
if(patchedTalkgroups.isEmpty())
{
mPatchGroupMap.remove(patchGroupID);
removePatchGroupAlias(patchGroupID);
updated = true;
}
else
{
updatePatchAlias(patchGroupID, patchedTalkgroups);
}
}
}
return updated;
}
/**
* Adds/updates a patch group alias to the alias list containing aliases for each of the patched talkgroups. If the
* patch group alias already exists, any patched talkgroups will be removed from the existing patch group alias and
* replaced with the aliases corresponding to the patched talkgroup aliases.
*
* @param patchGroupID for the patch group
* @param patchedTalkgroups containing the talkgroup IDs for each of the patched talkgroups
*/
private void updatePatchAlias(String patchGroupID, List<String> patchedTalkgroups)
{
PatchGroupAlias patchGroupAlias = null;
//Check for an existing alias for the patch talkgroup - do not include wildcard aliases
Alias existingAlias = getAliasList().getTalkgroupAlias(patchGroupID, false);
if(existingAlias instanceof PatchGroupAlias)
{
patchGroupAlias = (PatchGroupAlias)existingAlias;
}
else
{
patchGroupAlias = new PatchGroupAlias();
patchGroupAlias.addAliasID(new TalkgroupID(patchGroupID));
if(existingAlias != null)
{
getAliasList().removeAlias(existingAlias);
patchGroupAlias.setPatchGroupAlias(existingAlias);
}
getAliasList().addAlias(patchGroupAlias);
}
if(patchGroupAlias != null)
{
patchGroupAlias.setPatchedTalkgroupIDs(patchedTalkgroups);
patchGroupAlias.clearPatchedAliases();
for(String patchedTalkgroup : patchedTalkgroups)
{
Alias patchedAlias = getAliasList().getTalkgroupAlias(patchedTalkgroup);
if(patchedAlias != null)
{
patchGroupAlias.addPatchedAlias(patchedAlias);
}
}
}
}
/**
* Removes the temporary patch group alias from the alias list
*/
private void removePatchGroupAlias(String patchGroupID)
{
Alias alias = getAliasList().getTalkgroupAlias(patchGroupID);
if(alias instanceof PatchGroupAlias)
{
PatchGroupAlias patchGroupAlias = (PatchGroupAlias)alias;
getAliasList().removeAlias(patchGroupAlias);
//Replace our temporary patch group alias with the original alias for the patch group ID
if(patchGroupAlias.hasPatchGroupAlias())
{
getAliasList().addAlias(patchGroupAlias.getPatchGroupAlias());
}
}
}
/**
* Process a traffic channel allocation message
*/
private void processTSBKChannelGrant(TSBKMessage message)
{
//Cleanup patch groups - auto-expire any patch groups before we allocate a channel
mPatchGroupManager.cleanupPatchGroups();
String channel = null;
String from = null;
String to = null;
@@ -3015,6 +2880,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(gdcg.isTDMAChannel() ? "TDMA " : "");
details.append(gdcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gdcg.getPriority()).append(" ");
details.append(gdcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3033,7 +2899,7 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
if(!mIgnoreDataCalls)
if(!mIgnoreDataCalls && !gdcg.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
@@ -3053,6 +2919,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(gvcg.isTDMAChannel() ? "TDMA " : "");
details.append(gvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcg.getPriority()).append(" ");
details.append(gvcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3071,12 +2938,13 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!gvcg.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
case GROUP_VOICE_CHANNEL_GRANT_UPDATE:
GroupVoiceChannelGrantUpdate gvcgu =
(GroupVoiceChannelGrantUpdate)message;
GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)message;
channel = gvcgu.getChannel1();
from = null;
@@ -3089,6 +2957,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(gvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgu.getPriority()).append(" ");
details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3096,7 +2965,6 @@ public class P25DecoderState extends DecoderState
P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL)
.aliasList(getAliasList())
.channel(channel)
.channel(gvcgu.getChannel1())
.details(details.toString())
.frequency(gvcgu.getDownlinkFrequency1())
@@ -3108,8 +2976,10 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!gvcgu.isTDMAChannel1())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
if(gvcgu.hasChannelNumber2())
{
@@ -3123,6 +2993,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(gvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgu.getPriority()).append(" ");
details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3140,8 +3011,10 @@ public class P25DecoderState extends DecoderState
broadcast(event2);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!gvcgu.isTDMAChannel2())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
}
break;
case GROUP_VOICE_CHANNEL_GRANT_UPDATE_EXPLICIT:
@@ -3160,6 +3033,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(gvcgue.isTDMAChannel() ? "TDMA " : "");
details.append(gvcgue.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(gvcgue.getPriority()).append(" ");
details.append(gvcgue.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3178,8 +3052,10 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!gvcgue.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
case INDIVIDUAL_DATA_CHANNEL_GRANT:
IndividualDataChannelGrant idcg = (IndividualDataChannelGrant)message;
@@ -3195,6 +3071,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(idcg.isTDMAChannel() ? "TDMA " : "");
details.append(idcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(idcg.getPriority()).append(" ");
details.append(idcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3213,10 +3090,9 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
if(!mIgnoreDataCalls)
if(!mIgnoreDataCalls && !idcg.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
case SNDCP_DATA_CHANNEL_GRANT:
@@ -3233,6 +3109,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(sdcg.isTDMAChannel() ? "TDMA " : "");
details.append(sdcg.isEmergency() ? "EMERGENCY " : "");
details.append(sdcg.isEncryptedChannel() ? "ENCRYPTED " : "");
details.append(sdcg.getSessionMode().name());
@@ -3250,15 +3127,13 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
if(!mIgnoreDataCalls)
if(!mIgnoreDataCalls && !sdcg.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT:
TelephoneInterconnectVoiceChannelGrant tivcg =
(TelephoneInterconnectVoiceChannelGrant)message;
TelephoneInterconnectVoiceChannelGrant tivcg = (TelephoneInterconnectVoiceChannelGrant)message;
channel = tivcg.getChannel();
from = null;
@@ -3273,6 +3148,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(tivcg.isTDMAChannel() ? "TDMA " : "");
details.append(tivcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(tivcg.getPriority()).append(" ");
details.append(tivcg.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3293,15 +3169,15 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!tivcg.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
case TELEPHONE_INTERCONNECT_VOICE_CHANNEL_GRANT_UPDATE:
TelephoneInterconnectVoiceChannelGrantUpdate tivcgu =
(TelephoneInterconnectVoiceChannelGrantUpdate)message;
TelephoneInterconnectVoiceChannelGrantUpdate tivcgu = (TelephoneInterconnectVoiceChannelGrantUpdate)message;
channel = tivcgu.getChannelIdentifier() + "-" +
tivcgu.getChannelNumber();
channel = tivcgu.getChannelIdentifier() + "-" + tivcgu.getChannelNumber();
from = null;
/* Address is ambiguous and could mean either source or target,
@@ -3315,6 +3191,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(tivcgu.isTDMAChannel() ? "TDMA " : "");
details.append(tivcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(tivcgu.getPriority()).append(" ");
details.append(tivcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3335,15 +3212,15 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!tivcgu.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT:
UnitToUnitVoiceChannelGrant uuvcg =
(UnitToUnitVoiceChannelGrant)message;
UnitToUnitVoiceChannelGrant uuvcg = (UnitToUnitVoiceChannelGrant)message;
channel = uuvcg.getChannelIdentifier() + "-" +
uuvcg.getChannelNumber();
channel = uuvcg.getChannelIdentifier() + "-" + uuvcg.getChannelNumber();
from = uuvcg.getSourceAddress();
to = uuvcg.getTargetAddress();
@@ -3354,6 +3231,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(uuvcg.isTDMAChannel() ? "TDMA " : "");
details.append(uuvcg.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(uuvcg.getPriority()).append(" ");
details.append(uuvcg.isEncryptedChannel() ? " ENCRYPTED " : "");
@@ -3373,15 +3251,15 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!uuvcg.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
case UNIT_TO_UNIT_VOICE_CHANNEL_GRANT_UPDATE:
UnitToUnitVoiceChannelGrantUpdate uuvcgu =
(UnitToUnitVoiceChannelGrantUpdate)message;
UnitToUnitVoiceChannelGrantUpdate uuvcgu = (UnitToUnitVoiceChannelGrantUpdate)message;
channel = uuvcgu.getChannelIdentifier() + "-" +
uuvcgu.getChannelNumber();
channel = uuvcgu.getChannelIdentifier() + "-" + uuvcgu.getChannelNumber();
from = uuvcgu.getSourceAddress();
to = uuvcgu.getTargetAddress();
@@ -3392,6 +3270,7 @@ public class P25DecoderState extends DecoderState
else
{
StringBuilder details = new StringBuilder();
details.append(uuvcgu.isTDMAChannel() ? "TDMA " : "");
details.append(uuvcgu.isEmergency() ? "EMERGENCY " : "");
details.append("PRI:").append(uuvcgu.getPriority()).append(" ");
details.append(uuvcgu.isEncryptedChannel() ? "ENCRYPTED " : "");
@@ -3411,8 +3290,10 @@ public class P25DecoderState extends DecoderState
broadcast(event);
}
broadcast(new TrafficChannelAllocationEvent(this,
mChannelCallMap.get(channel)));
if(!uuvcgu.isTDMAChannel())
{
broadcast(new TrafficChannelAllocationEvent(this, mChannelCallMap.get(channel)));
}
break;
default:
break;
@@ -3522,29 +3403,25 @@ public class P25DecoderState extends DecoderState
{
StringBuilder sb = new StringBuilder();
sb.append("Activity Summary\n");
sb.append("Decoder:\tP25\n");
sb.append("===================== THIS SITE ======================");
sb.append("Activity Summary - Decoder:P25 ").append(getModulation().getLabel()).append("\n");
sb.append(DIVIDER1);
sb.append("SITE ");
if(mNetworkStatus != null)
{
sb.append("\nNAC:\t" + mNetworkStatus.getNAC());
sb.append("\nWACN-SYS:\t" + mNetworkStatus.getWACN());
sb.append("-" + mNetworkStatus.getSystemID());
sb.append(" [");
sb.append(mNetworkStatus.getNetworkCallsign());
sb.append("]");
sb.append("\nLRA:\t" + mNetworkStatus.getLocationRegistrationArea());
sb.append("NAC:" + mNetworkStatus.getNAC());
sb.append("h WACN:" + mNetworkStatus.getWACN());
sb.append("h SYS:" + mNetworkStatus.getSystemID());
sb.append("h [").append(mNetworkStatus.getNetworkCallsign()).append("]");
sb.append(" LRA:" + mNetworkStatus.getLocationRegistrationArea());
}
else if(mNetworkStatusExtended != null)
{
sb.append("\nNAC:\t" + mNetworkStatusExtended.getNAC());
sb.append("\nWACN-SYS:\t" + mNetworkStatusExtended.getWACN());
sb.append("-" + mNetworkStatusExtended.getSystemID());
sb.append(" [");
sb.append(mNetworkStatusExtended.getNetworkCallsign());
sb.append("]");
sb.append("\nLRA:\t" + mNetworkStatusExtended.getLocationRegistrationArea());
sb.append("NAC:" + mNetworkStatusExtended.getNAC());
sb.append("h WACN:" + mNetworkStatusExtended.getWACN());
sb.append("h SYS:" + mNetworkStatusExtended.getSystemID());
sb.append("h [").append(mNetworkStatusExtended.getNetworkCallsign()).append("]");
sb.append(" LRA:" + mNetworkStatusExtended.getLocationRegistrationArea());
}
String site = null;
@@ -3555,11 +3432,10 @@ public class P25DecoderState extends DecoderState
}
else if(mRFSSStatusMessageExtended != null)
{
site = mRFSSStatusMessageExtended.getRFSubsystemID() + "-" +
mRFSSStatusMessageExtended.getSiteID();
site = mRFSSStatusMessageExtended.getRFSubsystemID() + "-" + mRFSSStatusMessageExtended.getSiteID();
}
sb.append("\nRFSS-SITE:\t" + site);
sb.append("h RFSS-SITE:").append(site).append("h");
if(hasAliasList())
{
@@ -3571,121 +3447,120 @@ public class P25DecoderState extends DecoderState
}
}
sb.append("\n").append(DIVIDER2);
if(mNetworkStatus != null)
{
sb.append("\nSERVICES:\t" + SystemService.toString(
mNetworkStatus.getSystemServiceClass()));
sb.append("\nPCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)mNetworkStatus.getDownlinkFrequency() / 1E6d) +
" [" + mNetworkStatus.getIdentifier() + "-" +
mNetworkStatus.getChannel() + "]\n");
sb.append("\tUPLINK " + mFrequencyFormatter.format(
(double)mNetworkStatus.getUplinkFrequency() / 1E6d) + " [" +
mNetworkStatus.getIdentifier() + "-" +
mNetworkStatus.getChannel() + "]\n");
sb.append("SERVICES: " + SystemService.toString(mNetworkStatus.getSystemServiceClass())).append("\n");
sb.append(DIVIDER2);
sb.append("PCCH DOWNLINK ")
.append(mFrequencyFormatter.format((double)mNetworkStatus.getDownlinkFrequency() / 1E6d))
.append(" [").append(mNetworkStatus.getIdentifier()).append("-").append(mNetworkStatus.getChannel())
.append("] UPLINK ")
.append(mFrequencyFormatter.format((double)mNetworkStatus.getUplinkFrequency() / 1E6d))
.append(" [").append(mNetworkStatus.getIdentifier()).append("-").append(mNetworkStatus.getChannel())
.append("]\n");
}
else if(mNetworkStatusExtended != null)
{
sb.append("\nSERVICES:\t" + SystemService.toString(
mNetworkStatusExtended.getSystemServiceClass()));
sb.append("\nPCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)mNetworkStatusExtended.getDownlinkFrequency() / 1E6d) +
" [" + mNetworkStatusExtended.getTransmitIdentifier() + "-" +
mNetworkStatusExtended.getTransmitChannel() + "]\n");
sb.append("\tUPLINK " + mFrequencyFormatter.format(
(double)mNetworkStatusExtended.getUplinkFrequency() / 1E6d) +
" [" + mNetworkStatusExtended.getReceiveIdentifier() + "-" +
mNetworkStatusExtended.getReceiveChannel() + "]");
sb.append("\nSERVICES:").append(SystemService.toString(mNetworkStatusExtended.getSystemServiceClass()))
.append("\n");
sb.append(DIVIDER2);
sb.append("PCCH DOWNLINK ")
.append(mFrequencyFormatter.format((double)mNetworkStatusExtended.getDownlinkFrequency() / 1E6d))
.append(" [").append(mNetworkStatusExtended.getTransmitIdentifier()).append("-")
.append(mNetworkStatusExtended.getTransmitChannel()).append("] UPLINK ")
.append(mFrequencyFormatter.format((double)mNetworkStatusExtended.getUplinkFrequency() / 1E6d))
.append(" [").append(mNetworkStatusExtended.getReceiveIdentifier()).append("-")
.append(mNetworkStatusExtended.getReceiveChannel()).append("]\n");
}
if(mSecondaryControlChannels.isEmpty())
{
sb.append("\nSCCH:\tNONE");
sb.append("SCCH: NONE\n");
}
else
{
for(module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast
sec : mSecondaryControlChannels)
for(module.decode.p25.message.tsbk.osp.control.SecondaryControlChannelBroadcast sec : mSecondaryControlChannels)
{
sb.append("\nSCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)sec.getDownlinkFrequency1() / 1E6d) +
" [" + sec.getIdentifier1() + "-" + sec.getChannel1() + "]\n");
sb.append("\tUPLINK " + mFrequencyFormatter.format(
(double)sec.getUplinkFrequency1() / 1E6d) + " [" +
sec.getIdentifier1() + "-" + sec.getChannel1() + "]\n");
sb.append("SCCH DOWNLINK ")
.append(mFrequencyFormatter.format((double)sec.getDownlinkFrequency1() / 1E6d))
.append(" [").append(sec.getIdentifier1()).append("-").append(sec.getChannel1()).append("] UPLINK ")
.append(mFrequencyFormatter.format((double)sec.getUplinkFrequency1() / 1E6d))
.append(" [").append(sec.getIdentifier1()).append("-").append(sec.getChannel1()).append("]");
if(sec.hasChannel2())
{
sb.append("\nSCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)sec.getDownlinkFrequency2() / 1E6d) +
" [" + sec.getIdentifier2() + "-" + sec.getChannel2() + "]\n");
sb.append("\tUPLINK " + mFrequencyFormatter.format(
(double)sec.getUplinkFrequency2() / 1E6d) + " [" +
sec.getIdentifier2() + "-" + sec.getChannel2() + "]");
sb.append(" SCCH 2 DOWNLINK ")
.append(mFrequencyFormatter.format((double)sec.getDownlinkFrequency2() / 1E6d))
.append(" [").append(sec.getIdentifier2()).append("-").append(sec.getChannel2())
.append("] UPLINK ").append(mFrequencyFormatter.format((double)sec.getUplinkFrequency2() / 1E6d))
.append(" [").append(sec.getIdentifier2()).append("-").append(sec.getChannel2()).append("]");
}
sb.append("\n");
}
}
if(mSNDCPDataChannel != null)
{
sb.append("\nSNDCP:");
sb.append("\tDNLINK " + mFrequencyFormatter.format(
(double)mSNDCPDataChannel.getDownlinkFrequency() / 1E6D) +
" [" + mSNDCPDataChannel.getTransmitChannel() + "]");
sb.append("\tUPLINK " + mFrequencyFormatter.format(
(double)mSNDCPDataChannel.getUplinkFrequency() / 1E6D) +
" [" + mSNDCPDataChannel.getReceiveChannel() + "]");
sb.append("SNDCP DOWNLINK ")
.append(mFrequencyFormatter.format((double)mSNDCPDataChannel.getDownlinkFrequency() / 1E6D))
.append(" [").append(mSNDCPDataChannel.getTransmitChannel()).append("]").append(" UPLINK ")
.append(mFrequencyFormatter.format((double)mSNDCPDataChannel.getUplinkFrequency() / 1E6D))
.append(" [").append(mSNDCPDataChannel.getReceiveChannel()).append("]\n");
}
if(mProtectionParameterBroadcast != null)
{
sb.append("\nENCRYPTION:");
sb.append("\nTYPE:\t" + mProtectionParameterBroadcast
.getEncryptionType().name());
sb.append("\nALGORITHM:\t" + mProtectionParameterBroadcast
.getAlgorithmID());
sb.append("\nKEY:\t" + mProtectionParameterBroadcast.getKeyID());
sb.append("\nINBOUND IV:\t" + mProtectionParameterBroadcast
.getInboundInitializationVector());
sb.append("\nOUTBOUND IV:\t" + mProtectionParameterBroadcast
.getOutboundInitializationVector());
sb.append(DIVIDER2);
sb.append("ENCRYPTION TYPE:").append(mProtectionParameterBroadcast.getEncryptionType().name());
sb.append(" ALGORITHM:").append(mProtectionParameterBroadcast.getAlgorithmID());
sb.append(" KEY:").append(mProtectionParameterBroadcast.getKeyID());
sb.append(" INBOUND IV:").append(mProtectionParameterBroadcast.getInboundInitializationVector());
sb.append(" OUTBOUND IV:").append(mProtectionParameterBroadcast.getOutboundInitializationVector());
sb.append("\n");
}
List<Integer> identifiers = new ArrayList<>(mBands.keySet());
Collections.sort(identifiers);
sb.append("\nFREQUENCY BANDS:");
sb.append(DIVIDER2).append("FREQUENCY BANDS:\n");
for(Integer id : identifiers)
{
IBandIdentifier band = mBands.get(id);
sb.append("\n\t" + id);
sb.append("- BASE: " + mFrequencyFormatter.format(
(double)band.getBaseFrequency() / 1E6d));
sb.append(" CHANNEL SIZE: " + mFrequencyFormatter.format(
(double)band.getChannelSpacing() / 1E6d));
sb.append(" UPLINK OFFSET: " + mFrequencyFormatter.format(
(double)band.getTransmitOffset() / 1E6D));
sb.append(band.toString()).append("\n");
// sb.append(" ").append(id);
// sb.append(" - BASE: " + mFrequencyFormatter.format(
// (double)band.getBaseFrequency() / 1E6d));
// sb.append(" CHANNEL SIZE: " + mFrequencyFormatter.format(
// (double)band.getChannelSpacing() / 1E6d));
// sb.append(" UPLINK OFFSET: " + mFrequencyFormatter.format(
// (double)band.getTransmitOffset() / 1E6D));
// sb.append("\n");
}
sb.append("\n\n=================== NEIGHBORS ======================");
sb.append(DIVIDER2).append("NEIGHBOR SITES: ");
if(mNeighborMap.isEmpty())
{
sb.append("\n\tNONE\n");
sb.append("\n----------------------------------------------------");
sb.append("NONE\n");
}
else
{
for(IAdjacentSite neighbor : mNeighborMap.values())
{
sb.append("\nNAC:\t" + ((P25Message)neighbor).getNAC());
sb.append("\nSYSTEM:\t" + neighbor.getSystemID());
sb.append("\nLRA:\t" + neighbor.getLRA());
sb.append("\n");
sb.append("NAC:").append(((P25Message)neighbor).getNAC());
sb.append("h SYSTEM:" + neighbor.getSystemID());
sb.append("h LRA:" + neighbor.getLRA());
String neighborID = neighbor.getRFSS() + "-" + neighbor.getSiteID();
sb.append("\nRFSS-SITE:\t" + neighborID);
sb.append("h RFSS-SITE:" + neighborID);
sb.append("h ");
if(hasAliasList())
{
@@ -3693,18 +3568,16 @@ public class P25DecoderState extends DecoderState
if(siteAlias != null)
{
sb.append(" " + siteAlias.getName());
sb.append(siteAlias.getName());
}
}
sb.append("\nPCCH:\tDNLINK " + mFrequencyFormatter.format(
(double)neighbor.getDownlinkFrequency() / 1E6d) +
" [" + neighbor.getDownlinkChannel() + "]");
sb.append("\n\tUPLINK:" + mFrequencyFormatter.format(
(double)neighbor.getUplinkFrequency() / 1E6d) +
" [" + neighbor.getDownlinkChannel() + "]\n");
sb.append("\nSERVICES:\t" + neighbor.getSystemServiceClass());
sb.append("\n----------------------------------------------------");
sb.append("\n PCCH: DOWNLINK ")
.append(mFrequencyFormatter.format((double)neighbor.getDownlinkFrequency() / 1E6d))
.append(" [").append(neighbor.getDownlinkChannel()).append("] UPLINK:")
.append(mFrequencyFormatter.format((double)neighbor.getUplinkFrequency() / 1E6d))
.append(" [").append(neighbor.getDownlinkChannel()).append("] SERVICES: ")
.append(neighbor.getSystemServiceClass());
}
}

View File

@@ -689,8 +689,7 @@ public class P25MessageFramer implements Listener<Dibit>
private LSMDemodulator mDemodulator;
private double mCorrection;
public CostasPhaseErrorDetector( FrameSync frameSync,
LSMDemodulator demodulator, double correction )
public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction )
{
super( frameSync.getSync() );

View File

@@ -1,19 +1,20 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This 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/>
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25;
@@ -41,18 +42,15 @@ import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
import dsp.filter.fir.real.RealFIRFilter_RB_RB;
public class P25_C4FMDecoder extends P25Decoder
implements IFrequencyChangeListener, IFrequencyChangeProvider,IFilteredRealBufferListener
public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener
{
private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class );
/* Instrumentation Taps */
private static final String INSTRUMENT_FILTER_OUTPUT = "Tap Point: Pre-Filter Output";
private static final String INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT = "Tap Point: Symbol Filter Output";
private static final String INSTRUMENT_C4FM_SLICER_OUTPUT = "Tap Point: C4FM Slicer Output";
private List<TapGroup> mAvailableTaps;
private RealFIRFilter_RB_RB mC4FMPreFilter;
private C4FMSymbolFilter mSymbolFilter;
private C4FMSlicer mC4FMSlicer;
private P25MessageFramer mMessageFramer;
@@ -74,15 +72,9 @@ public class P25_C4FMDecoder extends P25Decoder
{
super( aliasList );
/* Filter demodulated sample buffers */
float[] filter = FilterFactory.getLowPass( 48000, 2500, 4000, 80, WindowType.HANNING, true );
mC4FMPreFilter = new RealFIRFilter_RB_RB( filter, 1.0f );
/* Shape gain and frequency offsets to optimize sample stream */
mSymbolFilter = new C4FMSymbolFilter( frequencyCorrectionMaximum );
mC4FMPreFilter.setListener( mSymbolFilter );
/* Convert samples to symbols */
mC4FMSlicer = new C4FMSlicer();
mSymbolFilter.setListener( mC4FMSlicer );
@@ -100,9 +92,6 @@ public class P25_C4FMDecoder extends P25Decoder
{
super.dispose();
mC4FMPreFilter.dispose();
mC4FMPreFilter = null;
mSymbolFilter.dispose();
mSymbolFilter = null;
@@ -121,7 +110,7 @@ public class P25_C4FMDecoder extends P25Decoder
@Override
public Listener<RealBuffer> getFilteredRealBufferListener()
{
return mC4FMPreFilter;
return mSymbolFilter;
}
/**
@@ -136,8 +125,6 @@ public class P25_C4FMDecoder extends P25Decoder
TapGroup group = new TapGroup( "P25 C4FM Decoder" );
group.add( new FloatBufferTap( INSTRUMENT_FILTER_OUTPUT, 0, 1.0f ) );
group.add( new FloatTap( INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT, 0, 0.1f ) );
group.add( new DibitTap( INSTRUMENT_C4FM_SLICER_OUTPUT, 0, 0.1f ) );
@@ -165,11 +152,6 @@ public class P25_C4FMDecoder extends P25Decoder
switch( tap.getName() )
{
case INSTRUMENT_FILTER_OUTPUT:
FloatBufferTap filterOutput = (FloatBufferTap)tap;
mC4FMPreFilter.setListener( filterOutput );
filterOutput.setListener( mSymbolFilter );
break;
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
FloatTap symbolTap = (FloatTap)tap;
mSymbolFilter.setListener( symbolTap );
@@ -201,9 +183,6 @@ public class P25_C4FMDecoder extends P25Decoder
switch( tap.getName() )
{
case INSTRUMENT_FILTER_OUTPUT:
mC4FMPreFilter.setListener( mSymbolFilter );
break;
case INSTRUMENT_C4FM_SYMBOL_FILTER_OUTPUT:
mSymbolFilter.setListener( mC4FMSlicer );
break;

View File

@@ -42,6 +42,7 @@ import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB;
import dsp.gain.ComplexFeedForwardGainControl;
import dsp.psk.LSMDemodulator;
import dsp.psk.QPSKPolarSlicer;
import source.tuner.frequency.FrequencyChangeEvent;
public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
{
@@ -56,8 +57,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
private List<TapGroup> mAvailableTaps;
private ComplexFIRFilter_CB_CB mBasebandFilter;
private ComplexBufferToStreamConverter mStreamConverter =
new ComplexBufferToStreamConverter();
private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter();
private ComplexFeedForwardGainControl mAGC =
new ComplexFeedForwardGainControl( 32 );
private LSMDemodulator mLSMDemodulator = new LSMDemodulator();
@@ -107,8 +107,31 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener
mMessageFramer.dispose();
mMessageFramer = null;
}
@Override
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
}
@Override
public void removeFrequencyChangeListener()
{
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
return new Listener<FrequencyChangeEvent>()
{
@Override
public void receive(FrequencyChangeEvent frequencyChangeEvent)
{
//Ignored
}
};
}
@Override
public Listener<ComplexBuffer> getComplexBufferListener()
{
return mBasebandFilter;

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25;
import java.util.ArrayList;
import java.util.List;
public class PatchGroup
{
private long mExpireThreshold = 5000; //5 seconds
private long mLastUpdated;
private String mPatchGroupID;
private List<String> mPatchedGroups = new ArrayList<>();
/**
* Constructs a patch group with the specified patch group ID
* @param patchGroupID that identifies this patch group
*/
public PatchGroup(String patchGroupID)
{
mPatchGroupID = patchGroupID;
mLastUpdated = System.currentTimeMillis();
}
/**
* Patch group ID for this patch group
*/
public String getPatchGroupID()
{
return mPatchGroupID;
}
/**
* Adds the patched group to this patch group. Duplicate patched groups are ignored.
*
* Note: the patch group ID will not be added as a patched group, to avoid circular dependencies.
*
* @return true if the patched group was added to this patch group
*/
public boolean addPatchedGroup(String patchedGroup)
{
if(patchedGroup != null && !patchedGroup.equals(mPatchGroupID) && !mPatchedGroups.contains(patchedGroup))
{
mPatchedGroups.add(patchedGroup);
return true;
}
return false;
}
/**
* Patched talkgroups that are part of this patch group.
*/
public List<String> getPatchedGroups()
{
return mPatchedGroups;
}
/**
* Updates the timestamp for this patch group.
*/
public void updateTimestamp()
{
mLastUpdated = System.currentTimeMillis();
}
/**
* Indicates if this patch group is expired, meaning that it hasn't been updated in the past (expire
* threshold) seconds.
*/
public boolean isExpired()
{
return System.currentTimeMillis() - mLastUpdated > mExpireThreshold;
}
/**
* Sets the patch group expiration threshold (default = 5000 ms). A patch group is considered expired if
* no new updates have been received within the past (expire threshold) milliseconds
*
* @param expireThreshold in milliseconds
*/
public void setExpireThreshold(long expireThreshold)
{
mExpireThreshold = expireThreshold;
}
}

View File

@@ -0,0 +1,245 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25;
import alias.Alias;
import alias.AliasList;
import alias.PatchGroupAlias;
import alias.id.talkgroup.TalkgroupID;
import module.decode.event.CallEvent;
import sample.Broadcaster;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class PatchGroupManager
{
private AliasList mAliasList;
private List<PatchGroup> mPatchGroups = new ArrayList<>();
private Broadcaster<CallEvent> mCallEventBroadcaster;
/**
* Manages active P25 patch groups and auto-expires patch groups if not updated every 5 seconds.
*
* @param aliasList to keep updated with patch group changes
* @param broadcaster for rebroadcasting patch group related call events
*/
public PatchGroupManager(AliasList aliasList, Broadcaster<CallEvent> broadcaster)
{
mAliasList = aliasList;
mCallEventBroadcaster = broadcaster;
}
/**
* Removes the patch group.
*
* @param patchGroupID of the patch group to remove
*/
public void removePatchGroup(String patchGroupID)
{
removePatchGroup(patchGroupID, false);
}
/**
* Removes the patch group.
*
* @param patchGroupID of the patch group to remove
* @param expired true if the patch group is being removed because it expired, false if we receive a
* message commanding commanding a patch group delete.
*/
private void removePatchGroup(String patchGroupID, boolean expired)
{
PatchGroup patchGroup = getPatchGroup(patchGroupID);
if(patchGroup != null)
{
//Cleanup the alias list
if(mAliasList != null)
{
Alias alias = mAliasList.getTalkgroupAlias(patchGroupID);
if(alias instanceof PatchGroupAlias)
{
PatchGroupAlias patchGroupAlias = (PatchGroupAlias)alias;
mAliasList.removeAlias(patchGroupAlias);
//Replace our temporary patch group alias with the original alias for the patch group ID
if(patchGroupAlias.hasPatchGroupAlias())
{
mAliasList.addAlias(patchGroupAlias.getPatchGroupAlias());
}
}
}
mPatchGroups.remove(patchGroup);
if(mCallEventBroadcaster != null)
{
StringBuilder sb = new StringBuilder();
sb.append((expired ? "EXPIRED " : "DELETED "));
sb.append("PATCH GROUP:").append(patchGroup.getPatchGroupID());
sb.append(" ").append(patchGroup.getPatchedGroups());
mCallEventBroadcaster.broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_DELETE)
.to(patchGroup.getPatchGroupID())
.details(sb.toString())
.build());
}
}
}
/**
* Adds/updates a patch group alias to the alias list containing aliases for each of the patched talkgroups. If the
* patch group alias already exists, any patched talkgroups will be removed from the existing patch group alias and
* replaced with the aliases corresponding to the patched talkgroup aliases.
*
* @param patchGroupID for the patch group
* @param patchedTalkgroups containing the talkgroup IDs for each of the patched talkgroups
*/
public void updatePatchGroup(String patchGroupID, List<String> patchedTalkgroups)
{
boolean updated = false;
PatchGroup patchGroup = getPatchGroup(patchGroupID);
if(patchGroup == null)
{
//Remove any expired patch groups before we add a new one
cleanupPatchGroups();
patchGroup = new PatchGroup(patchGroupID);
mPatchGroups.add(patchGroup);
updated = true;
}
else
{
patchGroup.updateTimestamp();
}
for(String patchedGroup : patchedTalkgroups)
{
if(patchGroup.addPatchedGroup(patchedGroup))
{
updated = true;
}
}
//If we make any changes to the patch group, update the patch group alias
if(updated)
{
PatchGroupAlias patchGroupAlias = null;
//Check for an existing alias for the patch talkgroup - do not include wildcard aliases
if(mAliasList != null)
{
Alias existingAlias = mAliasList.getTalkgroupAlias(patchGroupID, false);
if(existingAlias instanceof PatchGroupAlias)
{
patchGroupAlias = (PatchGroupAlias)existingAlias;
}
else
{
patchGroupAlias = new PatchGroupAlias();
patchGroupAlias.addAliasID(new TalkgroupID(patchGroupID));
if(existingAlias != null)
{
mAliasList.removeAlias(existingAlias);
patchGroupAlias.setPatchGroupAlias(existingAlias);
}
mAliasList.addAlias(patchGroupAlias);
}
if(patchGroupAlias != null)
{
patchGroupAlias.setPatchedTalkgroupIDs(patchGroup.getPatchedGroups());
patchGroupAlias.clearPatchedAliases();
for(String patchedTalkgroup : patchGroup.getPatchedGroups())
{
Alias patchedAlias = mAliasList.getTalkgroupAlias(patchedTalkgroup);
if(patchedAlias != null)
{
patchGroupAlias.addPatchedAlias(patchedAlias);
}
}
}
}
if(mCallEventBroadcaster != null)
{
StringBuilder sb = new StringBuilder();
sb.append("PATCH GROUP:").append(patchGroup.getPatchGroupID());
sb.append(" ").append(patchGroup.getPatchedGroups());
mCallEventBroadcaster.broadcast(new P25CallEvent.Builder(CallEvent.CallEventType.PATCH_GROUP_ADD)
.aliasList(mAliasList)
.details(sb.toString())
.build());
}
}
}
/**
* Returns the patch group matching the patch group ID or null
*/
private PatchGroup getPatchGroup(String patchGroupID)
{
for(PatchGroup patchGroup : mPatchGroups)
{
if(patchGroup.getPatchGroupID().equals(patchGroupID))
{
return patchGroup;
}
}
return null;
}
/**
* Checks the last updated timestamp on all patch groups and removes any patch groups that have not been
* updated in the past 5 seconds
*/
public void cleanupPatchGroups()
{
Iterator<PatchGroup> it = mPatchGroups.iterator();
while(it.hasNext())
{
PatchGroup patchGroup = it.next();
if(patchGroup.isExpired())
{
it.remove();
removePatchGroup(patchGroup.getPatchGroupID(), true);
}
}
}
}

View File

@@ -12,6 +12,9 @@ import jmbe.iface.AudioConverter;
import message.IMessageListener;
import message.Message;
import module.Module;
import module.decode.p25.message.hdu.HDUMessage;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.ldu.LDU2Message;
import module.decode.p25.message.ldu.LDUMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,14 +28,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class);
private static final String IMBE_CODEC = "IMBE";
private static boolean mLibraryLoadStatusLogged = false;
private boolean mCanConvertAudio = false;
private boolean mEncryptedCall = false;
private boolean mEncryptedCallStateEstablished = false;
private AudioConverter mAudioConverter;
private Listener<AudioPacket> mAudioPacketListener;
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
private Metadata mMetadata;
private LDU1Message mCachedLDU1Message = null;
public P25AudioModule(Metadata metadata)
{
@@ -76,32 +83,73 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
}
/**
* Primary processing method for p25 imbe audio frame messages. Each LDU
* audio message contains 9 imbe voice frames. Each frame is converted to
* audio when the JMBE library is loaded, processed by auto-gain, and
* broadcast to the audio packet listener.
* Processes call header (HDU) and voice frame (LDU1/LDU2) messages to decode audio and to determine the
* encrypted audio status of a call event. Only the HDU and LDU2 messages convey encrypted call status. If an
* LDU1 message is received without a preceding HDU message, then the LDU1 message is cached until the first
* LDU2 message is received and the encryption state can be determined. Both the LDU1 and the LDU2 message are
* then processed for audio if the call is unencrypted.
*/
public void receive(Message message)
{
if (mCanConvertAudio && mAudioPacketListener != null)
if(mCanConvertAudio && mAudioPacketListener != null)
{
if (message instanceof LDUMessage)
if(mEncryptedCallStateEstablished)
{
LDUMessage ldu = (LDUMessage) message;
/* Only process unencrypted audio frames */
if (!(ldu.isValid() && ldu.isEncrypted()))
if(message instanceof LDUMessage)
{
for (byte[] frame : ldu.getIMBEFrames())
{
float[] audio = mAudioConverter.decode(frame);
audio = mGain.apply(audio);
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
}
processAudio((LDUMessage)message);
}
}
else
{
if(message instanceof HDUMessage)
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = ((HDUMessage)message).isEncryptedAudio();
}
else if(message instanceof LDU1Message)
{
//When we receive an LDU1 message with first receiving the HDU message, cache the LDU1 Message
//until we can determine the encrypted call state from the next LDU2 message
mCachedLDU1Message = (LDU1Message)message;
}
else if(message instanceof LDU2Message)
{
mEncryptedCallStateEstablished = true;
LDU2Message ldu2 = (LDU2Message)message;
mEncryptedCall = ldu2.isEncryptedAudio();
if(mCachedLDU1Message != null)
{
processAudio(mCachedLDU1Message);
mCachedLDU1Message = null;
}
processAudio(ldu2);
}
}
}
}
/**
* Processes an audio packet by decoding the IMBE audio frames and rebroadcasting them as PCM audio packets.
*/
private void processAudio(LDUMessage ldu)
{
if(!mEncryptedCall)
{
for(byte[] frame : ldu.getIMBEFrames())
{
float[] audio = mAudioConverter.decode(frame);
audio = mGain.apply(audio);
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
}
}
else
{
//Encrypted audio processing not implemented
}
}
@@ -119,18 +167,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
@SuppressWarnings("rawtypes")
Class temp = Class.forName("jmbe.JMBEAudioLibrary");
library = (AudioConversionLibrary) temp.newInstance();
library = (AudioConversionLibrary)temp.newInstance();
if ((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 &&
library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1)
if((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 &&
library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1)
{
mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
if (mAudioConverter != null)
if(mAudioConverter != null)
{
mCanConvertAudio = true;
if (!mLibraryLoadStatusLogged)
if(!mLibraryLoadStatusLogged)
{
StringBuilder sb = new StringBuilder();
sb.append("JMBE audio conversion library [");
@@ -144,7 +192,7 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
}
else
{
if (!mLibraryLoadStatusLogged)
if(!mLibraryLoadStatusLogged)
{
mLog.info("JMBE audio conversion library NOT FOUND");
mLibraryLoadStatusLogged = true;
@@ -156,28 +204,28 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
mLog.warn("JMBE library version 0.3.3 or higher is required - found: " + library.getVersion());
}
}
catch (ClassNotFoundException e1)
catch(ClassNotFoundException e1)
{
if (!mLibraryLoadStatusLogged)
if(!mLibraryLoadStatusLogged)
{
mLog.error("Couldn't find/load JMBE audio conversion library");
mLibraryLoadStatusLogged = true;
}
}
catch (InstantiationException e1)
catch(InstantiationException e1)
{
if (!mLibraryLoadStatusLogged)
if(!mLibraryLoadStatusLogged)
{
mLog.error("Couldn't instantiate JMBE audio conversion library class");
mLibraryLoadStatusLogged = true;
}
}
catch (IllegalAccessException e1)
catch(IllegalAccessException e1)
{
if (!mLibraryLoadStatusLogged)
if(!mLibraryLoadStatusLogged)
{
mLog.error("Couldn't load JMBE audio conversion library due to "
+ "security restrictions");
+ "security restrictions");
mLibraryLoadStatusLogged = true;
}
}
@@ -196,19 +244,25 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
}
/**
* Wrapper for squelch state listener. Internally, the P25 audio module
* doesn't have a squelch state. If there are IMBE audio frames, we have
* audio. We use this listener to signal to the recorder manager that the
* channel state is resetting and it should end the recording.
* Wrapper for squelch state to process end of call actions. At call end the encrypted call state established
* flag is reset so that the encrypted audio state for the next call can be properly detected and we send an
* END audio packet so that downstream processors like the audio recorder can properly close out a call sequence.
*/
public class SquelchStateListener implements Listener<SquelchState>
{
@Override
public void receive(SquelchState state)
{
if (state == SquelchState.SQUELCH && mAudioPacketListener != null)
if(state == SquelchState.SQUELCH)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
if(mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
}
mEncryptedCallStateEstablished = false;
mEncryptedCall = false;
mCachedLDU1Message = null;
}
}
}

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message;
public interface IBandIdentifier
@@ -29,4 +47,18 @@ public interface IBandIdentifier
*/
public abstract long getTransmitOffset();
/**
* Downlink (tower to mobile) Frequency for the specified channel
*/
public abstract long getDownlinkFrequency(int channelNumber);
/**
* Uplink (mobile to tower) Frequency for the specified channel
*/
public abstract long getUplinkFrequency(int channelNumber);
/**
* Indicates if this band is an FDMA (false) or TDMA (true) band
*/
public boolean isTDMA();
}

View File

@@ -1,214 +1,188 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message;
import java.text.SimpleDateFormat;
import map.Plottable;
import message.Message;
import module.decode.p25.reference.DataUnitID;
import alias.Alias;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import map.Plottable;
import message.Message;
import module.decode.p25.reference.DataUnitID;
import java.text.SimpleDateFormat;
public class P25Message extends Message
{
public enum DuplexMode { HALF, FULL };
public enum SessionMode { PACKET, CIRCUIT };
public static final int[] NAC = { 0,1,2,3,4,5,6,7,8,9,10,11 };
public static final int[] DUID = { 12,13,14,15 };
public static final int[] BCH = { 16,17,18,19,20,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,
54,55,56,57,58,59,60,61,62,63 };
protected SimpleDateFormat mTimeDurationFormat =
new SimpleDateFormat( "HH:mm:ss.SSS");
protected BinaryMessage mMessage;
protected DataUnitID mDUID;
protected AliasList mAliasList;
protected CRC[] mCRC;
public P25Message( BinaryMessage message,
DataUnitID duid,
AliasList aliasList )
{
super();
mMessage = message;
mDUID = duid;
mAliasList = aliasList;
}
public CRC[] getCRCResults()
{
return mCRC;
}
public BinaryMessage getSourceMessage()
{
return mMessage;
}
public AliasList getAliasList()
{
return mAliasList;
}
public String getNAC()
{
return mMessage.getHex( NAC, 3 );
}
public DataUnitID getDUID()
{
return mDUID;
}
@Override
public enum DuplexMode {HALF, FULL};
public enum SessionMode {PACKET, CIRCUIT};
public static final int[] NAC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
public static final int[] DUID = {12, 13, 14, 15};
public static final int[] BCH = {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63};
protected SimpleDateFormat mTimeDurationFormat = new SimpleDateFormat("HH:mm:ss.SSS");
protected BinaryMessage mMessage;
protected DataUnitID mDUID;
protected AliasList mAliasList;
protected CRC[] mCRC;
public P25Message(BinaryMessage message, DataUnitID duid, AliasList aliasList)
{
super();
mMessage = message;
mDUID = duid;
mAliasList = aliasList;
}
public CRC[] getCRCResults()
{
return mCRC;
}
public BinaryMessage getSourceMessage()
{
return mMessage;
}
public AliasList getAliasList()
{
return mAliasList;
}
public String getNAC()
{
return mMessage.getHex(NAC, 3);
}
public DataUnitID getDUID()
{
return mDUID;
}
@Override
public String getErrorStatus()
{
if( mCRC == null )
{
return "UNKNOWN";
}
return CRC.format( mCRC );
if(mCRC == null)
{
return "UNKNOWN";
}
return CRC.format(mCRC);
}
@Override
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( "NAC:" );
sb.append( getNAC() );
sb.append( " " );
sb.append( getDUID().getLabel() );
sb.append( " DATA UNIT ID:" + mMessage.getInt( DUID ) );
return sb.toString();
StringBuilder sb = new StringBuilder();
sb.append("NAC:");
sb.append(getNAC());
sb.append(" ");
sb.append(getDUID().getLabel());
sb.append(" DATA UNIT ID:" + mMessage.getInt(DUID));
return sb.toString();
}
@Override
@Override
public String getBinaryMessage()
{
return mMessage.toString();
return mMessage.toString();
}
@Override
@Override
public String getProtocol()
{
return "P25 Phase 1";
return "P25 Phase 1";
}
@Override
@Override
public String getEventType()
{
return mDUID.name();
return mDUID.name();
}
@Override
@Override
public String getFromID()
{
return null;
}
@Override
public Alias getFromIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getTalkgroupAlias( getFromID() );
}
return null;
}
@Override
@Override
public Alias getFromIDAlias()
{
if(mAliasList != null)
{
return mAliasList.getTalkgroupAlias(getFromID());
}
return null;
}
@Override
public String getToID()
{
return null;
return null;
}
@Override
@Override
public Alias getToIDAlias()
{
if( mAliasList != null )
{
return mAliasList.getTalkgroupAlias( getToID() );
}
return null;
if(mAliasList != null)
{
return mAliasList.getTalkgroupAlias(getToID());
}
return null;
}
@Override
@Override
public Plottable getPlottable()
{
return null;
return null;
}
@Override
@Override
public String toString()
{
return getMessage();
return getMessage();
}
@Override
@Override
public boolean isValid()
{
if( mCRC != null )
{
for( CRC crc: mCRC )
{
if( crc == CRC.FAILED_CRC )
{
return false;
}
}
}
return true;
}
if(mCRC != null)
{
for(CRC crc : mCRC)
{
if(crc == CRC.FAILED_CRC)
{
return false;
}
}
}
/**
* Calculates the frequency of the uplink channel using the channel number
* and the IdentifierUpdate message.
*
* @param iden - Identifier Update message
* @param channel - channel number
* @return - frequency in Hertz
*/
protected static long calculateUplink( IBandIdentifier iden, int channel )
{
long downlink = calculateDownlink( iden, channel );
if( downlink > 0 && iden != null )
{
return downlink + iden.getTransmitOffset();
}
return 0;
}
/**
* Calculates the frequency of the downlink channel using the channel number
* and the IdentifierUpdate message.
*
* @param iden - Identifier Update message
* @param channel - channel number
* @return - frequency in Hertz
*/
protected static long calculateDownlink( IBandIdentifier iden, int channel )
{
if( iden != null )
{
return iden.getBaseFrequency() +
( channel * iden.getChannelSpacing() );
}
return 0;
}
return true;
}
}

View File

@@ -245,7 +245,7 @@ public class HDUMessage extends P25Message
return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) );
}
public boolean isEncrypted()
public boolean isEncryptedAudio()
{
return getEncryption() != Encryption.UNENCRYPTED;
}

View File

@@ -1,244 +1,246 @@
package module.decode.p25.message.ldu;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList;
import bits.BinaryMessage;
import edac.CRC;
import edac.Hamming10;
import edac.ReedSolomon_63_47_17;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LDU1Message extends LDUMessage
{
private final static Logger mLog = LoggerFactory.getLogger( LDU1Message.class );
public static final int ENCRYPTION_FLAG = 352;
public static final int IMPLICIT_VENDOR_FLAG = 353;
public static final int[] OPCODE = { 354,355,356,357,362,363 };
public static final int[] VENDOR = { 364,365,366,367,372,373,374,375 };
public static final int[] GOLAY_WORD_STARTS = {
352,362,372,382,536,546,556,566,720,730,740,750,904,914,924,934,1088,
1098,1108,1118,1272,1282,1292,1302 };
private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class);
public static final int[] CW_HEX_0 = { 352,353,354,355,356,357 };
public static final int[] CW_HEX_1 = { 362,363,364,365,366,367 };
public static final int[] CW_HEX_2 = { 372,373,374,375,376,377 };
public static final int[] CW_HEX_3 = { 382,383,384,385,386,387 };
public static final int[] CW_HEX_4 = { 536,537,538,539,540,541 };
public static final int[] CW_HEX_5 = { 546,547,548,549,550,551 };
public static final int[] CW_HEX_6 = { 556,557,558,559,560,561 };
public static final int[] CW_HEX_7 = { 566,567,568,569,570,571 };
public static final int[] CW_HEX_8 = { 720,721,722,723,724,725 };
public static final int[] CW_HEX_9 = { 730,731,732,733,734,735 };
public static final int[] CW_HEX_10 = { 740,741,742,743,744,745 };
public static final int[] CW_HEX_11 = { 750,751,752,753,754,755 };
public static final int[] RS_HEX_0 = { 904,905,906,907,908,909 };
public static final int[] RS_HEX_1 = { 914,915,916,917,918,919 };
public static final int[] RS_HEX_2 = { 924,925,926,927,928,929 };
public static final int[] RS_HEX_3 = { 934,935,936,937,938,939 };
public static final int[] RS_HEX_4 = { 1088,1089,1090,1091,1092,1093 };
public static final int[] RS_HEX_5 = { 1098,1099,1100,1101,1102,1103 };
public static final int[] RS_HEX_6 = { 1108,1109,1110,1111,1112,1113 };
public static final int[] RS_HEX_7 = { 1118,1119,1120,1121,1122,1123 };
public static final int[] RS_HEX_8 = { 1272,1273,1274,1275,1276,1277 };
public static final int[] RS_HEX_9 = { 1282,1283,1284,1285,1286,1287 };
public static final int[] RS_HEX_10 = { 1292,1293,1294,1295,1296,1297 };
public static final int[] RS_HEX_11 = { 1302,1303,1304,1305,1306,1307 };
public static final int ENCRYPTED_LINK_CONTROL_WORD_FLAG = 352;
public static final int IMPLICIT_VENDOR_FLAG = 353;
public static final int[] OPCODE = {354, 355, 356, 357, 362, 363};
public static final int[] VENDOR = {364, 365, 366, 367, 372, 373, 374, 375};
/* Reed-Solomon(24,12,13) code protects the link control word. Maximum
* correctable errors are: Hamming Distance(13) / 2 = 6 */
public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
new ReedSolomon_63_47_17( 6 );
public static final int[] GOLAY_WORD_STARTS = {
352, 362, 372, 382, 536, 546, 556, 566, 720, 730, 740, 750, 904, 914, 924, 934, 1088,
1098, 1108, 1118, 1272, 1282, 1292, 1302};
public LDU1Message( BinaryMessage message, DataUnitID duid,
AliasList aliasList )
public static final int[] CW_HEX_0 = {352, 353, 354, 355, 356, 357};
public static final int[] CW_HEX_1 = {362, 363, 364, 365, 366, 367};
public static final int[] CW_HEX_2 = {372, 373, 374, 375, 376, 377};
public static final int[] CW_HEX_3 = {382, 383, 384, 385, 386, 387};
public static final int[] CW_HEX_4 = {536, 537, 538, 539, 540, 541};
public static final int[] CW_HEX_5 = {546, 547, 548, 549, 550, 551};
public static final int[] CW_HEX_6 = {556, 557, 558, 559, 560, 561};
public static final int[] CW_HEX_7 = {566, 567, 568, 569, 570, 571};
public static final int[] CW_HEX_8 = {720, 721, 722, 723, 724, 725};
public static final int[] CW_HEX_9 = {730, 731, 732, 733, 734, 735};
public static final int[] CW_HEX_10 = {740, 741, 742, 743, 744, 745};
public static final int[] CW_HEX_11 = {750, 751, 752, 753, 754, 755};
public static final int[] RS_HEX_0 = {904, 905, 906, 907, 908, 909};
public static final int[] RS_HEX_1 = {914, 915, 916, 917, 918, 919};
public static final int[] RS_HEX_2 = {924, 925, 926, 927, 928, 929};
public static final int[] RS_HEX_3 = {934, 935, 936, 937, 938, 939};
public static final int[] RS_HEX_4 = {1088, 1089, 1090, 1091, 1092, 1093};
public static final int[] RS_HEX_5 = {1098, 1099, 1100, 1101, 1102, 1103};
public static final int[] RS_HEX_6 = {1108, 1109, 1110, 1111, 1112, 1113};
public static final int[] RS_HEX_7 = {1118, 1119, 1120, 1121, 1122, 1123};
public static final int[] RS_HEX_8 = {1272, 1273, 1274, 1275, 1276, 1277};
public static final int[] RS_HEX_9 = {1282, 1283, 1284, 1285, 1286, 1287};
public static final int[] RS_HEX_10 = {1292, 1293, 1294, 1295, 1296, 1297};
public static final int[] RS_HEX_11 = {1302, 1303, 1304, 1305, 1306, 1307};
/* Reed-Solomon(24,12,13) code protects the link control word. Maximum
* correctable errors are: Hamming Distance(13) / 2 = 6 */
public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
new ReedSolomon_63_47_17(6);
public LDU1Message(BinaryMessage message, DataUnitID duid,
AliasList aliasList)
{
super( message, duid, aliasList );
super(message, duid, aliasList);
/* NID CRC is checked in the message framer, thus a constructed message
* means it passed the CRC */
mCRC = new CRC[ 3 ];
mCRC[ 0 ] = CRC.PASSED;
checkCRC();
}
/**
* Subclass constructor from an existing LDU1Message
*/
protected LDU1Message( LDU1Message message )
{
super( message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList() );
mCRC = message.mCRC;
}
* means it passed the CRC */
mCRC = new CRC[3];
mCRC[0] = CRC.PASSED;
@Override
public boolean isEncrypted()
{
return mMessage.get( ENCRYPTION_FLAG );
}
private void checkCRC()
{
mCRC[ 1 ] = CRC.PASSED;
checkCRC();
}
/**
* Subclass constructor from an existing LDU1Message
*/
protected LDU1Message(LDU1Message message)
{
super(message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList());
mCRC = message.mCRC;
}
/**
* Indicates if the contents of the Link Control Word are encrypted.
*
* @return true if the contents of the LCW are encrypted.
*/
public boolean isEncryptedLinkControlWord()
{
return mMessage.get(ENCRYPTED_LINK_CONTROL_WORD_FLAG);
}
private void checkCRC()
{
mCRC[1] = CRC.PASSED;
/* Hamming( 10,6,3 ) error detection and correction */
for( int index: GOLAY_WORD_STARTS )
{
int errors = Hamming10.checkAndCorrect( mMessage, index );
if( errors > 1 )
{
mCRC[ 1 ] = CRC.FAILED_CRC;
}
}
for(int index : GOLAY_WORD_STARTS)
{
int errors = Hamming10.checkAndCorrect(mMessage, index);
if(errors > 1)
{
mCRC[1] = CRC.FAILED_CRC;
}
}
/* Reed-Solomon( 24,16,9 ) error detection and correction
* Check the Reed-Solomon parity bits. The RS decoder expects the code
* words and reed solomon parity hex codewords in reverse order.
*
* Since this is a truncated RS(63) codes, we pad the code with zeros */
int[] input = new int [63];
int[] output = new int [63];
input[ 0 ] = mMessage.getInt( RS_HEX_11 );
input[ 1 ] = mMessage.getInt( RS_HEX_10 );
input[ 2 ] = mMessage.getInt( RS_HEX_9 );
input[ 3 ] = mMessage.getInt( RS_HEX_8 );
input[ 4 ] = mMessage.getInt( RS_HEX_7 );
input[ 5 ] = mMessage.getInt( RS_HEX_6 );
input[ 6 ] = mMessage.getInt( RS_HEX_5 );
input[ 7 ] = mMessage.getInt( RS_HEX_4 );
input[ 8 ] = mMessage.getInt( RS_HEX_3 );
input[ 9 ] = mMessage.getInt( RS_HEX_2 );
input[ 10 ] = mMessage.getInt( RS_HEX_1 );
input[ 11 ] = mMessage.getInt( RS_HEX_0 );
input[ 12 ] = mMessage.getInt( CW_HEX_11 );
input[ 13 ] = mMessage.getInt( CW_HEX_10 );
input[ 14 ] = mMessage.getInt( CW_HEX_9 );
input[ 15 ] = mMessage.getInt( CW_HEX_8 );
input[ 16 ] = mMessage.getInt( CW_HEX_7 );
input[ 17 ] = mMessage.getInt( CW_HEX_6 );
input[ 18 ] = mMessage.getInt( CW_HEX_5 );
input[ 19 ] = mMessage.getInt( CW_HEX_4 );
input[ 20 ] = mMessage.getInt( CW_HEX_3 );
input[ 21 ] = mMessage.getInt( CW_HEX_2 );
input[ 22 ] = mMessage.getInt( CW_HEX_1 );
input[ 23 ] = mMessage.getInt( CW_HEX_0 );
int[] input = new int[63];
int[] output = new int[63];
input[0] = mMessage.getInt(RS_HEX_11);
input[1] = mMessage.getInt(RS_HEX_10);
input[2] = mMessage.getInt(RS_HEX_9);
input[3] = mMessage.getInt(RS_HEX_8);
input[4] = mMessage.getInt(RS_HEX_7);
input[5] = mMessage.getInt(RS_HEX_6);
input[6] = mMessage.getInt(RS_HEX_5);
input[7] = mMessage.getInt(RS_HEX_4);
input[8] = mMessage.getInt(RS_HEX_3);
input[9] = mMessage.getInt(RS_HEX_2);
input[10] = mMessage.getInt(RS_HEX_1);
input[11] = mMessage.getInt(RS_HEX_0);
input[12] = mMessage.getInt(CW_HEX_11);
input[13] = mMessage.getInt(CW_HEX_10);
input[14] = mMessage.getInt(CW_HEX_9);
input[15] = mMessage.getInt(CW_HEX_8);
input[16] = mMessage.getInt(CW_HEX_7);
input[17] = mMessage.getInt(CW_HEX_6);
input[18] = mMessage.getInt(CW_HEX_5);
input[19] = mMessage.getInt(CW_HEX_4);
input[20] = mMessage.getInt(CW_HEX_3);
input[21] = mMessage.getInt(CW_HEX_2);
input[22] = mMessage.getInt(CW_HEX_1);
input[23] = mMessage.getInt(CW_HEX_0);
/* indexes 24 - 62 are defaulted to zero */
boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output );
boolean irrecoverableErrors = mReedSolomonDecoder.decode(input, output);
if( irrecoverableErrors )
if(irrecoverableErrors)
{
mCRC[ 2 ] = CRC.FAILED_CRC;
mCRC[2] = CRC.FAILED_CRC;
}
else
{
/* Since we've detected that we can correct the RS words, mark the
* hamming crc as corrected as well, if it is currently failed */
if( mCRC[ 1 ] == CRC.FAILED_CRC )
{
mCRC[ 1 ] = CRC.CORRECTED;
}
mCRC[ 2 ] = CRC.PASSED;
if(mCRC[1] == CRC.FAILED_CRC)
{
mCRC[1] = CRC.CORRECTED;
}
mCRC[2] = CRC.PASSED;
/* Only fix the codeword data hex codewords */
repairHexCodeword( input, output, 12, CW_HEX_11 );
repairHexCodeword( input, output, 13, CW_HEX_10 );
repairHexCodeword( input, output, 14, CW_HEX_9 );
repairHexCodeword( input, output, 15, CW_HEX_8 );
repairHexCodeword( input, output, 16, CW_HEX_7 );
repairHexCodeword( input, output, 17, CW_HEX_6 );
repairHexCodeword( input, output, 18, CW_HEX_5 );
repairHexCodeword( input, output, 19, CW_HEX_4 );
repairHexCodeword( input, output, 20, CW_HEX_3 );
repairHexCodeword( input, output, 21, CW_HEX_2 );
repairHexCodeword( input, output, 22, CW_HEX_1 );
repairHexCodeword( input, output, 23, CW_HEX_0 );
repairHexCodeword(input, output, 12, CW_HEX_11);
repairHexCodeword(input, output, 13, CW_HEX_10);
repairHexCodeword(input, output, 14, CW_HEX_9);
repairHexCodeword(input, output, 15, CW_HEX_8);
repairHexCodeword(input, output, 16, CW_HEX_7);
repairHexCodeword(input, output, 17, CW_HEX_6);
repairHexCodeword(input, output, 18, CW_HEX_5);
repairHexCodeword(input, output, 19, CW_HEX_4);
repairHexCodeword(input, output, 20, CW_HEX_3);
repairHexCodeword(input, output, 21, CW_HEX_2);
repairHexCodeword(input, output, 22, CW_HEX_1);
repairHexCodeword(input, output, 23, CW_HEX_0);
}
}
private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet )
{
if( input[ index ] != output[ index ] )
{
mMessage.load( indexSet[ 0 ], 6, output[ index ] );
mCRC[ 2 ] = CRC.CORRECTED;
}
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " " );
sb.append( mMessage.toString() );
return sb.toString();
}
public String getMessageStub()
{
StringBuilder sb = new StringBuilder();
sb.append( super.getMessageStub() );
if( isEncrypted() )
{
sb.append( "ENCRYPTED" );
}
else
{
if( isImplicitFormat() || getVendor() == Vendor.STANDARD )
{
sb.append( getOpcode().getLabel() );
}
else
{
sb.append( "VENDOR:" + getVendor().getLabel() + " " );
sb.append( getVendorOpcode().getLabel() );
}
}
return sb.toString();
}
public boolean isImplicitFormat()
{
return mMessage.get( IMPLICIT_VENDOR_FLAG );
}
public LinkControlOpcode getOpcode()
{
return LinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) );
}
private void repairHexCodeword(int[] input, int[] output, int index, int[] indexSet)
{
if(input[index] != output[index])
{
mMessage.load(indexSet[0], 6, output[index]);
mCRC[2] = CRC.CORRECTED;
}
}
public VendorLinkControlOpcode getVendorOpcode()
{
return VendorLinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) );
}
public Vendor getVendor()
{
if( isImplicitFormat() )
{
return Vendor.STANDARD;
}
return Vendor.fromValue( mMessage.getInt( VENDOR ) );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" ");
sb.append(mMessage.toString());
return sb.toString();
}
public String getMessageStub()
{
StringBuilder sb = new StringBuilder();
sb.append(super.getMessageStub());
if(isEncryptedLinkControlWord())
{
sb.append("ENCRYPTED");
}
else
{
if(isImplicitFormat() || getVendor() == Vendor.STANDARD)
{
sb.append(getOpcode().getLabel());
}
else
{
sb.append("VENDOR:" + getVendor().getLabel() + " ");
sb.append(getVendorOpcode().getLabel());
}
}
return sb.toString();
}
public boolean isImplicitFormat()
{
return mMessage.get(IMPLICIT_VENDOR_FLAG);
}
public LinkControlOpcode getOpcode()
{
return LinkControlOpcode.fromValue(mMessage.getInt(OPCODE));
}
public VendorLinkControlOpcode getVendorOpcode()
{
return VendorLinkControlOpcode.fromValue(mMessage.getInt(OPCODE));
}
public Vendor getVendor()
{
if(isImplicitFormat())
{
return Vendor.STANDARD;
}
return Vendor.fromValue(mMessage.getInt(VENDOR));
}
}

View File

@@ -72,9 +72,11 @@ public class LDU2Message extends LDUMessage
checkCRC();
}
@Override
public boolean isEncrypted()
/**
* Indicates if the audio stream is encrypted
*/
public boolean isEncryptedAudio()
{
Encryption encryption = getEncryption();

View File

@@ -35,8 +35,6 @@ public abstract class LDUMessage extends P25Message
super( message, duid, aliasList );
}
public abstract boolean isEncrypted();
@Override
public String getMessage()
{

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IAdjacentSite;
@@ -7,142 +25,148 @@ import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
public class AdjacentSiteStatusBroadcast extends LDU1Message
implements IdentifierReceiver, IAdjacentSite
public class AdjacentSiteStatusBroadcast extends LDU1Message implements IdentifierReceiver, IAdjacentSite
{
public static final int[] LRA = { 364,365,366,367,372,373,374,375 };
public static final int[] SYSTEM_ID = { 384,385,386,387,536,537,538,539,540,
541,546,547 };
public static final int[] RFSS_ID = { 548,549,550,551,556,557,558,559 };
public static final int[] SITE_ID = { 560,561,566,567,568,569,570,571 };
public static final int[] IDENTIFIER = { 720,721,722,723 };
public static final int[] CHANNEL = { 724,725,730,731,732,733,734,735,740,
741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS = { 744,745,750,751,752,753,
754,755 };
private IBandIdentifier mIdentifierUpdate;
public AdjacentSiteStatusBroadcast( LDU1Message message )
{
super( message );
}
public static final int[] LRA = {364, 365, 366, 367, 372, 373, 374, 375};
public static final int[] SYSTEM_ID = {384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, 547};
public static final int[] RFSS_ID = {548, 549, 550, 551, 556, 557, 558, 559};
public static final int[] SITE_ID = {560, 561, 566, 567, 568, 569, 570, 571};
public static final int[] IDENTIFIER = {720, 721, 722, 723};
public static final int[] CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743};
public static final int[] SYSTEM_SERVICE_CLASS = {744, 745, 750, 751, 752, 753, 754, 755};
private IBandIdentifier mIdentifierUpdate;
public AdjacentSiteStatusBroadcast(LDU1Message message)
{
super(message);
}
@Override
public String getEventType()
{
return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLRA() );
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( " SYS:" + getSystemID() );
sb.append( " SITE:" + getRFSS() + "-" + getSiteID() );
sb.append( " CHAN:" + getChannel() );
sb.append(getMessageStub());
sb.append(" LRA:" + getLRA());
sb.append(" SYS:" + getSystemID());
sb.append(" SITE:" + getRFSS() + "-" + getSiteID());
sb.append(" CHAN:" + getChannel());
sb.append(" " + getSystemServiceClass());
return sb.toString();
}
sb.append( " " + getSystemServiceClass() );
return sb.toString();
}
public String getUniqueID()
{
StringBuilder sb = new StringBuilder();
sb.append( getSystemID() );
sb.append( ":" );
sb.append( getRFSS() );
sb.append( ":" );
sb.append( getSiteID() );
return sb.toString();
}
public String getLRA()
{
return mMessage.getHex( LRA, 2 );
}
public String getSystemID()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getRFSS()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getChannelNumber()
{
return mMessage.getInt( CHANNEL );
}
public String getSystemServiceClass()
{
return SystemService.toString( mMessage.getInt( SYSTEM_SERVICE_CLASS ) );
}
StringBuilder sb = new StringBuilder();
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
sb.append(getSystemID());
sb.append(":");
sb.append(getRFSS());
sb.append(":");
sb.append(getSiteID());
return sb.toString();
}
public String getLRA()
{
mIdentifierUpdate = message;
return mMessage.getHex(LRA, 2);
}
@Override
public String getSystemID()
{
return mMessage.getHex(SYSTEM_ID, 3);
}
public String getRFSS()
{
return mMessage.getHex(RFSS_ID, 2);
}
public String getSiteID()
{
return mMessage.getHex(SITE_ID, 2);
}
public int getIdentifier()
{
return mMessage.getInt(IDENTIFIER);
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getChannelNumber()
{
return mMessage.getInt(CHANNEL);
}
public String getSystemServiceClass()
{
return SystemService.toString(mMessage.getInt(SYSTEM_SERVICE_CLASS));
}
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
mIdentifierUpdate = message;
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getIdentifier();
return identifiers;
int[] identifiers = new int[1];
identifiers[0] = getIdentifier();
return identifiers;
}
@Override
public String getDownlinkChannel()
{
return getChannel();
}
@Override
public String getUplinkChannel()
{
return getChannel();
}
public long getDownlinkFrequency()
public String getDownlinkChannel()
{
return calculateDownlink( mIdentifierUpdate, getChannelNumber() );
return getChannel();
}
@Override
public String getUplinkChannel()
{
return getChannel();
}
public long getDownlinkFrequency()
{
if(mIdentifierUpdate != null)
{
return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber());
}
return 0;
}
public long getUplinkFrequency()
{
return calculateUplink( mIdentifierUpdate, getChannelNumber() );
if(mIdentifierUpdate != null)
{
return mIdentifierUpdate.getUplinkFrequency(getChannelNumber());
}
return 0;
}
}

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IAdjacentSite;
@@ -6,154 +24,160 @@ import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
public class AdjacentSiteStatusBroadcastExplicit extends LDU1Message
implements IdentifierReceiver, IAdjacentSite
public class AdjacentSiteStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver, IAdjacentSite
{
public static final int[] LRA = { 364,365,366,367,372,373,374,375 };
public static final int[] TRANSMIT_IDENTIFIER = { 376,377,382,383 };
public static final int[] TRANSMIT_CHANNEL = { 384,385,386,387,536,537,538,
539,540,541,546,547 };
public static final int[] RFSS_ID = { 548,549,550,551,556,557,558,559 };
public static final int[] SITE_ID = { 560,561,566,567,568,569,570,571 };
public static final int[] RECEIVE_IDENTIFIER = { 720,721,722,723 };
public static final int[] RECEIVE_CHANNEL = { 724,725,730,731,732,733,734,
735,740,741,742,743 };
private IBandIdentifier mTransmitBand;
private IBandIdentifier mReceiveBand;
public AdjacentSiteStatusBroadcastExplicit( LDU1Message message )
{
super( message );
}
public static final int[] LRA = {364, 365, 366, 367, 372, 373, 374, 375};
public static final int[] TRANSMIT_IDENTIFIER = {376, 377, 382, 383};
public static final int[] TRANSMIT_CHANNEL = {384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, 547};
public static final int[] RFSS_ID = {548, 549, 550, 551, 556, 557, 558, 559};
public static final int[] SITE_ID = {560, 561, 566, 567, 568, 569, 570, 571};
public static final int[] RECEIVE_IDENTIFIER = {720, 721, 722, 723};
public static final int[] RECEIVE_CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743};
private IBandIdentifier mTransmitBand;
private IBandIdentifier mReceiveBand;
public AdjacentSiteStatusBroadcastExplicit(LDU1Message message)
{
super(message);
}
@Override
public String getEventType()
{
return LinkControlOpcode.ADJACENT_SITE_STATUS_BROADCAST_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLRA() );
sb.append( " SITE:" + getRFSS() + "-" + getSiteID() );
sb.append( " DNLINK:" + getDownlinkChannel() );
sb.append( " UPLINK:" + getUplinkChannel() );
return sb.toString();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" LRA:" + getLRA());
sb.append(" SITE:" + getRFSS() + "-" + getSiteID());
sb.append(" DNLINK:" + getDownlinkChannel());
sb.append(" UPLINK:" + getUplinkChannel());
return sb.toString();
}
public String getUniqueID()
{
StringBuilder sb = new StringBuilder();
sb.append( getSystemID() );
sb.append( ":" );
sb.append( getRFSS() );
sb.append( ":" );
sb.append( getSiteID() );
return sb.toString();
}
public String getLRA()
{
return mMessage.getHex( LRA, 2 );
}
public String getRFSS()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSystemID()
{
return "***";
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannel()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public String getDownlinkChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannel();
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannel()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public String getUplinkChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannel();
}
StringBuilder sb = new StringBuilder();
@Override
public String getSystemServiceClass()
{
return "[]";
}
sb.append(getSystemID());
sb.append(":");
sb.append(getRFSS());
sb.append(":");
sb.append(getSiteID());
return sb.toString();
}
public String getLRA()
{
return mMessage.getHex(LRA, 2);
}
public String getRFSS()
{
return mMessage.getHex(RFSS_ID, 2);
}
public String getSystemID()
{
return "***";
}
public String getSiteID()
{
return mMessage.getHex(SITE_ID, 2);
}
public int getTransmitIdentifier()
{
return mMessage.getInt(TRANSMIT_IDENTIFIER);
}
public int getTransmitChannel()
{
return mMessage.getInt(TRANSMIT_CHANNEL);
}
public String getDownlinkChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannel();
}
public int getReceiveIdentifier()
{
return mMessage.getInt(RECEIVE_IDENTIFIER);
}
public int getReceiveChannel()
{
return mMessage.getInt(RECEIVE_CHANNEL);
}
public String getUplinkChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannel();
}
@Override
public String getSystemServiceClass()
{
return "[]";
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitBand, getTransmitChannel() );
if(mTransmitBand != null)
{
return mTransmitBand.getDownlinkFrequency(getTransmitChannel());
}
return 0;
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveBand, getReceiveChannel() );
if(mReceiveBand != null)
{
return mReceiveBand.getUplinkFrequency(getReceiveChannel());
}
return 0;
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitIdentifier() )
{
mTransmitBand = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveBand = message;
}
}
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
if(identifier == getTransmitIdentifier())
{
mTransmitBand = message;
}
if(identifier == getReceiveIdentifier())
{
mReceiveBand = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[2];
identifiers[0] = getTransmitIdentifier();
identifiers[1] = getReceiveIdentifier();
return identifiers;
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getTransmitIdentifier();
identifiers[ 1 ] = getReceiveIdentifier();
return identifiers;
}
}

View File

@@ -1,83 +1,118 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
public class ChannelIdentifierUpdate extends LDU1Message
implements IBandIdentifier
public class ChannelIdentifierUpdate extends LDU1Message implements IBandIdentifier
{
public static final int[] IDENTIFIER = { 364,365,366,367 };
public static final int[] BANDWIDTH = { 372,373,374,375,376,377,382,383,384 };
public static final int[] TRANSMIT_OFFSET = { 385,386,387,536,537,538,539,
540,541 };
public static final int[] CHANNEL_SPACING = { 546,547,548,549,550,551,556,
557,558,559 };
public static final int[] BASE_FREQUENCY = { 560,561,566,567,568,569,570,
571,720,721,722,723,724,725,730,731,732,733,734,735,740,741,742,743,744,
745,750,751,752,753,754,755 };
public static final int[] IDENTIFIER = {364, 365, 366, 367};
public static final int[] BANDWIDTH = {372, 373, 374, 375, 376, 377, 382, 383, 384};
public static final int[] TRANSMIT_OFFSET = {385, 386, 387, 536, 537, 538, 539,
540, 541};
public static final int[] CHANNEL_SPACING = {546, 547, 548, 549, 550, 551, 556,
557, 558, 559};
public static final int[] BASE_FREQUENCY = {560, 561, 566, 567, 568, 569, 570,
571, 720, 721, 722, 723, 724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743, 744,
745, 750, 751, 752, 753, 754, 755};
public ChannelIdentifierUpdate(LDU1Message message)
{
super(message);
}
public ChannelIdentifierUpdate( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.CHANNEL_IDENTIFIER_UPDATE.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " IDEN:" + getIdentifier() );
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( " BASE:" + getBaseFrequency() );
sb.append( " BW:" + getBandwidth() );
sb.append( " SPACING:" + getChannelSpacing() );
sb.append( " OFFSET:" + getTransmitOffset() );
return sb.toString();
}
sb.append(getMessageStub());
@Override
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
sb.append(" IDEN:" + getIdentifier());
/**
sb.append(" BASE:" + getBaseFrequency());
sb.append(" BW:" + getBandwidth());
sb.append(" SPACING:" + getChannelSpacing());
sb.append(" OFFSET:" + getTransmitOffset());
return sb.toString();
}
@Override
public int getIdentifier()
{
return mMessage.getInt(IDENTIFIER);
}
/**
* Channel bandwidth in hertz
*/
@Override
public int getBandwidth()
{
return mMessage.getInt( BANDWIDTH ) * 125;
return mMessage.getInt(BANDWIDTH) * 125;
}
@Override
public long getChannelSpacing()
{
return mMessage.getLong( CHANNEL_SPACING ) * 125l;
return mMessage.getLong(CHANNEL_SPACING) * 125l;
}
@Override
public long getBaseFrequency()
{
return mMessage.getLong( BASE_FREQUENCY ) * 5l;
return mMessage.getLong(BASE_FREQUENCY) * 5l;
}
@Override
@Override
public long getTransmitOffset()
{
return -1 * mMessage.getLong( TRANSMIT_OFFSET ) * 250000l;
return -1 * mMessage.getLong(TRANSMIT_OFFSET) * 250000l;
}
@Override
public long getDownlinkFrequency(int channelNumber)
{
return getBaseFrequency() + (channelNumber * getChannelSpacing());
}
@Override
public long getUplinkFrequency(int channelNumber)
{
return getDownlinkFrequency(channelNumber) + getTransmitOffset();
}
@Override
public boolean isTDMA()
{
return false;
}
}

View File

@@ -4,8 +4,7 @@ import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
public class ChannelIdentifierUpdateExplicit extends LDU1Message
implements IBandIdentifier
public class ChannelIdentifierUpdateExplicit extends LDU1Message implements IBandIdentifier
{
public static final int[] IDENTIFIER = { 364,365,366,367 };
public static final int[] BANDWIDTH = { 372,373,374,375 };
@@ -80,4 +79,22 @@ public class ChannelIdentifierUpdateExplicit extends LDU1Message
{
return -1 * mMessage.getLong( TRANSMIT_OFFSET ) * 250000l;
}
@Override
public long getDownlinkFrequency(int channelNumber)
{
return getBaseFrequency() + ( channelNumber * getChannelSpacing() );
}
@Override
public long getUplinkFrequency(int channelNumber)
{
return getDownlinkFrequency(channelNumber) + getTransmitOffset();
}
@Override
public boolean isTDMA()
{
return false;
}
}

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
@@ -5,156 +23,167 @@ import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GroupVoiceChannelUpdate extends LDU1Message
implements IdentifierReceiver
public class GroupVoiceChannelUpdate extends LDU1Message implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( GroupVoiceChannelUpdate.class );
public static final int[] IDENTIFIER_A = {364, 365, 366, 367};
public static final int[] CHANNEL_A = {372, 373, 374, 375, 376, 377, 382, 383, 384, 385, 386, 387};
public static final int[] GROUP_ADDRESS_A = {536, 537, 538, 539, 540, 541, 546, 547, 548, 549, 550, 551, 556, 557,
558, 559};
public static final int[] IDENTIFIER_B = {560, 561, 566, 567};
public static final int[] CHANNEL_B = {568, 569, 570, 571, 720, 721, 722, 723, 724, 725, 730, 731};
public static final int[] GROUP_ADDRESS_B = {732, 733, 734, 735, 740, 741, 742, 743, 744, 745, 750, 751, 752, 753,
754, 755};
private IBandIdentifier mIdentifierUpdateA;
private IBandIdentifier mIdentifierUpdateB;
public GroupVoiceChannelUpdate(LDU1Message message)
{
super(message);
}
public static final int[] IDENTIFIER_A = { 364,365,366,367 };
public static final int[] CHANNEL_A = { 372,373,374,375,376,377,382,383,384,
385,386,387 };
public static final int[] GROUP_ADDRESS_A = { 536,537,538,539,540,541,546,
547,548,549,550,551,556,557,558,559 };
public static final int[] IDENTIFIER_B = { 560,561,566,567 };
public static final int[] CHANNEL_B = { 568,569,570,571,720,721,722,723,724,
725,730,731 };
public static final int[] GROUP_ADDRESS_B = { 732,733,734,735,740,741,742,
743,744,745,750,751,752,753,754,755 };
private IBandIdentifier mIdentifierUpdateA;
private IBandIdentifier mIdentifierUpdateB;
public GroupVoiceChannelUpdate( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.GROUP_VOICE_CHANNEL_UPDATE.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " CHAN A:" + getChannelA() );
sb.append( " GRP A:" + getGroupAddressA() );
if( hasChannelB() )
{
sb.append( " CHAN B:" + getChannelA() );
sb.append( " GRP B:" + getGroupAddressA() );
}
return sb.toString();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" CHAN A:" + getChannelA());
sb.append(" GRP A:" + getGroupAddressA());
if(hasChannelB())
{
sb.append(" CHAN B:" + getChannelA());
sb.append(" GRP B:" + getGroupAddressA());
}
return sb.toString();
}
public int getChannelIdentifierA()
{
return mMessage.getInt(IDENTIFIER_A);
}
public int getChannelIdentifierA()
{
return mMessage.getInt( IDENTIFIER_A );
}
public int getChannelNumberA()
{
return mMessage.getInt( CHANNEL_A );
return mMessage.getInt(CHANNEL_A);
}
public String getChannelA()
{
return getChannelIdentifierA() + "-" + getChannelNumberA();
return getChannelIdentifierA() + "-" + getChannelNumberA();
}
public int getChannelIdentifierB()
{
return mMessage.getInt( IDENTIFIER_B );
}
public int getChannelIdentifierB()
{
return mMessage.getInt(IDENTIFIER_B);
}
public int getChannelNumberB()
{
return mMessage.getInt( CHANNEL_B );
return mMessage.getInt(CHANNEL_B);
}
public String getChannelB()
{
return getChannelIdentifierB() + "-" + getChannelNumberB();
return getChannelIdentifierB() + "-" + getChannelNumberB();
}
public boolean hasChannelB()
{
return getChannelNumberB() != 0;
return getChannelNumberB() != 0;
}
public String getGroupAddressA()
{
return mMessage.getHex( GROUP_ADDRESS_A, 4 );
return mMessage.getHex(GROUP_ADDRESS_A, 4);
}
public String getGroupAddressB()
{
return mMessage.getHex( GROUP_ADDRESS_B, 4 );
return mMessage.getHex(GROUP_ADDRESS_B, 4);
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
if( identifier == getChannelIdentifierA() )
{
mIdentifierUpdateA = message;
}
if( identifier == getChannelIdentifierB() )
{
mIdentifierUpdateB = message;
}
if(identifier == getChannelIdentifierA())
{
mIdentifierUpdateA = message;
}
if(identifier == getChannelIdentifierB())
{
mIdentifierUpdateB = message;
}
}
@Override
@Override
public int[] getIdentifiers()
{
if( hasChannelB() )
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getChannelIdentifierA();
identifiers[ 1 ] = getChannelIdentifierB();
return identifiers;
}
else
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getChannelIdentifierA();
return identifiers;
}
if(hasChannelB())
{
int[] identifiers = new int[2];
identifiers[0] = getChannelIdentifierA();
identifiers[1] = getChannelIdentifierB();
return identifiers;
}
else
{
int[] identifiers = new int[1];
identifiers[0] = getChannelIdentifierA();
return identifiers;
}
}
public long getDownlinkFrequencyA()
{
return calculateDownlink( mIdentifierUpdateA, getChannelNumberA() );
if(mIdentifierUpdateA != null)
{
return mIdentifierUpdateA.getDownlinkFrequency(getChannelNumberA());
}
return 0;
}
public long getUplinkFrequencyA()
{
return calculateUplink( mIdentifierUpdateA, getChannelNumberA() );
if(mIdentifierUpdateA != null)
{
return mIdentifierUpdateA.getUplinkFrequency(getChannelNumberA());
}
return 0;
}
public long getDownlinkFrequencyB()
{
return calculateDownlink( mIdentifierUpdateB, getChannelNumberA() );
if(mIdentifierUpdateB != null)
{
return mIdentifierUpdateB.getDownlinkFrequency(getChannelNumberB());
}
return 0;
}
public long getUplinkFrequencyB()
{
return calculateUplink( mIdentifierUpdateB, getChannelNumberA() );
if(mIdentifierUpdateB != null)
{
return mIdentifierUpdateB.getUplinkFrequency(getChannelNumberB());
}
return 0;
}
}

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
@@ -5,151 +23,149 @@ import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GroupVoiceChannelUpdateExplicit extends LDU1Message
implements IdentifierReceiver
public class GroupVoiceChannelUpdateExplicit extends LDU1Message implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( GroupVoiceChannelUpdateExplicit.class );
public static final int EMERGENCY_FLAG = 376;
public static final int ENCRYPTED_CHANNEL_FLAG = 377;
public static final int DUPLEX_MODE = 382;
public static final int SESSION_MODE = 383;
public static final int[] GROUP_ADDRESS = { 536,537,538,539,540,541,546,547,
548,549,550,551,556,557,558,559 };
public static final int[] TRANSMIT_IDENTIFIER = { 560,561,566,567 };
public static final int[] TRANSMIT_CHANNEL = { 568,569,570,571,720,721,722,
723,724,725,730,731 };
public static final int[] RECEIVE_IDENTIFIER = { 732,733,734,735 };
public static final int[] RECEIVE_CHANNEL = { 740,741,742,743,744,745,750,
751,752,753,754,755 };
public static final int[] GROUP_ADDRESS = {536, 537, 538, 539, 540, 541, 546, 547, 548, 549, 550, 551, 556, 557, 558, 559};
public static final int[] TRANSMIT_IDENTIFIER = {560, 561, 566, 567};
public static final int[] TRANSMIT_CHANNEL = {568, 569, 570, 571, 720, 721, 722, 723, 724, 725, 730, 731};
public static final int[] RECEIVE_IDENTIFIER = {732, 733, 734, 735};
public static final int[] RECEIVE_CHANNEL = {740, 741, 742, 743, 744, 745, 750, 751, 752, 753, 754, 755};
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public GroupVoiceChannelUpdateExplicit(LDU1Message message)
{
super(message);
}
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public GroupVoiceChannelUpdateExplicit( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.GROUP_VOICE_CHANNEL_UPDATE_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
if( isEmergency() )
{
sb.append( " EMERGENCY" );
}
if( isEncryptedChannel() )
{
sb.append( " ENCRYPTED CHANNEL" );
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
return sb.toString();
}
sb.append(getMessageStub());
if(isEmergency())
{
sb.append(" EMERGENCY");
}
if(isEncryptedChannel())
{
sb.append(" ENCRYPTED CHANNEL");
}
return sb.toString();
}
public boolean isEmergency()
{
return mMessage.get( EMERGENCY_FLAG );
return mMessage.get(EMERGENCY_FLAG);
}
public boolean isEncryptedChannel()
{
return mMessage.get( ENCRYPTED_CHANNEL_FLAG );
return mMessage.get(ENCRYPTED_CHANNEL_FLAG);
}
public DuplexMode getDuplexMode()
{
return mMessage.get( DUPLEX_MODE ) ? DuplexMode.FULL : DuplexMode.HALF;
return mMessage.get(DUPLEX_MODE) ? DuplexMode.FULL : DuplexMode.HALF;
}
public SessionMode getSessionMode()
{
return mMessage.get( SESSION_MODE ) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
return mMessage.get(SESSION_MODE) ?
SessionMode.CIRCUIT : SessionMode.PACKET;
}
public int getTransmitChannelIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public String getTransmitChannel()
{
return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveChannelIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public String getReceiveChannel()
{
return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber();
}
public String getGroupAddress()
{
return mMessage.getHex( GROUP_ADDRESS, 4 );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
{
if( identifier == getTransmitChannelIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveChannelIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
return mMessage.getInt(TRANSMIT_IDENTIFIER);
}
@Override
public int getTransmitChannelNumber()
{
return mMessage.getInt(TRANSMIT_CHANNEL);
}
public String getTransmitChannel()
{
return getTransmitChannelIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveChannelIdentifier()
{
return mMessage.getInt(RECEIVE_IDENTIFIER);
}
public int getReceiveChannelNumber()
{
return mMessage.getInt(RECEIVE_CHANNEL);
}
public String getReceiveChannel()
{
return getReceiveChannelIdentifier() + "-" + getReceiveChannelNumber();
}
public String getGroupAddress()
{
return mMessage.getHex(GROUP_ADDRESS, 4);
}
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
if(identifier == getTransmitChannelIdentifier())
{
mTransmitIdentifierUpdate = message;
}
if(identifier == getReceiveChannelIdentifier())
{
mReceiveIdentifierUpdate = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getTransmitChannelIdentifier();
identifiers[ 1 ] = getReceiveChannelIdentifier();
return identifiers;
int[] identifiers = new int[2];
identifiers[0] = getTransmitChannelIdentifier();
identifiers[1] = getReceiveChannelIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate,
getTransmitChannelNumber() );
if(mTransmitIdentifierUpdate != null)
{
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
}
return 0;
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate,
getReceiveChannelNumber() );
if(mReceiveIdentifierUpdate != null)
{
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
}
return 0;
}
}

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
@@ -6,105 +24,106 @@ import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NetworkStatusBroadcast extends LDU1Message
implements IdentifierReceiver
public class NetworkStatusBroadcast extends LDU1Message implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( NetworkStatusBroadcast.class );
public static final int[] NETWORK_ID = { 376,377,382,383,384,385,386,387,
536,537,538,539,540,541,546,547,548,549,550,551 };
public static final int[] SYSTEM_ID = { 556,557,558,559,560,561,566,567,568,
569,570,571 };
public static final int[] IDENTIFIER = { 720,721,722,723 };
public static final int[] CHANNEL = { 724,725,730,731,732,733,734,735,740,
741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS = { 744,745,750,751,752,753,
754,755 };
private IBandIdentifier mIdentifierUpdate;
public NetworkStatusBroadcast( LDU1Message source )
{
super( source );
}
public static final int[] NETWORK_ID = {376, 377, 382, 383, 384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546,
547, 548, 549, 550, 551};
public static final int[] SYSTEM_ID = {556, 557, 558, 559, 560, 561, 566, 567, 568, 569, 570, 571};
public static final int[] IDENTIFIER = {720, 721, 722, 723};
public static final int[] CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743};
public static final int[] SYSTEM_SERVICE_CLASS = {744, 745, 750, 751, 752, 753, 754, 755};
private IBandIdentifier mIdentifierUpdate;
public NetworkStatusBroadcast(LDU1Message source)
{
super(source);
}
@Override
public String getEventType()
{
return LinkControlOpcode.NETWORK_STATUS_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " NETWORK:" + getNetworkID() );
sb.append( " SYS:" + getSystem() );
sb.append( " CHAN:" + getChannel() );
sb.append( " " + Service.getServices( getSystemServiceClass() ).toString() );
return sb.toString();
}
public String getNetworkID()
{
return mMessage.getHex( NETWORK_ID, 5 );
}
public String getSystem()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
public int getChannelNumber()
{
return mMessage.getInt( CHANNEL );
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
@Override
public String getMessage()
{
mIdentifierUpdate = message;
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" NETWORK:" + getNetworkID());
sb.append(" SYS:" + getSystem());
sb.append(" CHAN:" + getChannel());
sb.append(" " + Service.getServices(getSystemServiceClass()).toString());
return sb.toString();
}
@Override
public String getNetworkID()
{
return mMessage.getHex(NETWORK_ID, 5);
}
public String getSystem()
{
return mMessage.getHex(SYSTEM_ID, 3);
}
public int getIdentifier()
{
return mMessage.getInt(IDENTIFIER);
}
public int getChannelNumber()
{
return mMessage.getInt(CHANNEL);
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getSystemServiceClass()
{
return mMessage.getInt(SYSTEM_SERVICE_CLASS);
}
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
mIdentifierUpdate = message;
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getIdentifier();
return identifiers;
int[] identifiers = new int[1];
identifiers[0] = getIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mIdentifierUpdate, getChannelNumber() );
if(mIdentifierUpdate != null)
{
return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber());
}
return 0;
}
public long getUplinkFrequency()
{
return calculateUplink( mIdentifierUpdate, getChannelNumber() );
if(mIdentifierUpdate != null)
{
return mIdentifierUpdate.getUplinkFrequency(getChannelNumber());
}
return 0;
}
}

View File

@@ -1,131 +1,150 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NetworkStatusBroadcastExplicit extends LDU1Message
implements IdentifierReceiver
public class NetworkStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( NetworkStatusBroadcastExplicit.class );
public static final int[] NETWORK_ID = { 364,365,366,367,372,373,374,375,
376,377,382,383,384,385,386,387,536,537,538,539 };
public static final int[] SYSTEM_ID = { 540,541,546,547,548,549,550,551,556,
557,558,559 };
public static final int[] TRANSMIT_IDENTIFIER = { 560,561,566,567 };
public static final int[] TRANSMIT_CHANNEL = { 568,569,570,571,720,721,722,
723,724,725,730,731 };
public static final int[] RECEIVE_IDENTIFIER = { 732,733,734,735 };
public static final int[] RECEIVE_CHANNEL = { 740,741,742,743,744,745,750,
751,752,753,754,755 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public NetworkStatusBroadcastExplicit( LDU1Message message )
{
super( message );
}
public static final int[] NETWORK_ID = {364, 365, 366, 367, 372, 373, 374, 375, 376, 377, 382, 383, 384, 385, 386,
387, 536, 537, 538, 539};
public static final int[] SYSTEM_ID = {540, 541, 546, 547, 548, 549, 550, 551, 556, 557, 558, 559};
public static final int[] TRANSMIT_IDENTIFIER = {560, 561, 566, 567};
public static final int[] TRANSMIT_CHANNEL = {568, 569, 570, 571, 720, 721, 722, 723, 724, 725, 730, 731};
public static final int[] RECEIVE_IDENTIFIER = {732, 733, 734, 735};
public static final int[] RECEIVE_CHANNEL = {740, 741, 742, 743, 744, 745, 750, 751, 752, 753, 754, 755};
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public NetworkStatusBroadcastExplicit(LDU1Message message)
{
super(message);
}
@Override
public String getEventType()
{
return LinkControlOpcode.NETWORK_STATUS_BROADCAST_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " NETWORK:" + getNetworkID() );
sb.append( " SYS:" + getSystem() );
sb.append( " TRANSMIT:" + getTransmitChannel() );
sb.append( " RECEIVE:" + getReceiveChannel() );
return sb.toString();
}
public String getNetworkID()
{
return mMessage.getHex( NETWORK_ID, 5 );
}
public String getSystem()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public String getTransmitChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public String getReceiveChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannelNumber();
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
@Override
public String getMessage()
{
if( identifier == getTransmitIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" NETWORK:" + getNetworkID());
sb.append(" SYS:" + getSystem());
sb.append(" TRANSMIT:" + getTransmitChannel());
sb.append(" RECEIVE:" + getReceiveChannel());
return sb.toString();
}
@Override
public String getNetworkID()
{
return mMessage.getHex(NETWORK_ID, 5);
}
public String getSystem()
{
return mMessage.getHex(SYSTEM_ID, 3);
}
public int getTransmitIdentifier()
{
return mMessage.getInt(TRANSMIT_IDENTIFIER);
}
public int getTransmitChannelNumber()
{
return mMessage.getInt(TRANSMIT_CHANNEL);
}
public String getTransmitChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannelNumber();
}
public int getReceiveIdentifier()
{
return mMessage.getInt(RECEIVE_IDENTIFIER);
}
public int getReceiveChannelNumber()
{
return mMessage.getInt(RECEIVE_CHANNEL);
}
public String getReceiveChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannelNumber();
}
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
if(identifier == getTransmitIdentifier())
{
mTransmitIdentifierUpdate = message;
}
if(identifier == getReceiveIdentifier())
{
mReceiveIdentifierUpdate = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getTransmitIdentifier();
identifiers[ 1 ] = getReceiveIdentifier();
return identifiers;
int[] identifiers = new int[2];
identifiers[0] = getTransmitIdentifier();
identifiers[1] = getReceiveIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate,
getTransmitChannelNumber() );
if(mTransmitIdentifierUpdate != null)
{
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
}
return 0;
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate,
getReceiveChannelNumber() );
if(mReceiveIdentifierUpdate != null)
{
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
}
return 0;
}
}

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
@@ -6,125 +24,124 @@ import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RFSSStatusBroadcast extends LDU1Message
implements IdentifierReceiver
public class RFSSStatusBroadcast extends LDU1Message implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( RFSSStatusBroadcast.class );
public static final int[] LRA = {364, 365, 366, 367, 372, 373, 374, 375};
public static final int[] SYSTEM_ID = {384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, 547};
public static final int[] RFSS_ID = {548, 549, 550, 551, 556, 557, 558, 559};
public static final int[] SITE_ID = {560, 561, 566, 567, 568, 569, 570, 571};
public static final int[] IDENTIFIER = {720, 721, 722, 723};
public static final int[] CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743};
public static final int[] SYSTEM_SERVICE_CLASS = {744, 745, 750, 751, 752, 753, 754, 755};
private IBandIdentifier mIdentifierUpdate;
public RFSSStatusBroadcast(LDU1Message message)
{
super(message);
}
public static final int[] LRA = { 364,365,366,367,372,373,374,375 };
public static final int[] SYSTEM_ID = { 384,385,386,387,536,537,538,539,540,
541,546,547 };
public static final int[] RFSS_ID = { 548,549,550,551,556,557,558,559 };
public static final int[] SITE_ID = { 560,561,566,567,568,569,570,571 };
public static final int[] IDENTIFIER = { 720,721,722,723 };
public static final int[] CHANNEL = { 724,725,730,731,732,733,734,735,740,
741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS = { 744,745,750,751,752,753,
754,755 };
private IBandIdentifier mIdentifierUpdate;
public RFSSStatusBroadcast( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.RFSS_STATUS_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SYS:" + getSystem() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " CHAN:" + getChannelNumber() );
sb.append( " " + SystemService.toString( getSystemServiceClass() ) );
sb.append( " " + mMessage.toString() );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getSystem()
{
return mMessage.getHex( SYSTEM_ID, 3 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getIdentifier()
{
return mMessage.getInt( IDENTIFIER );
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getChannelNumber()
{
return mMessage.getInt( CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
@Override
public String getMessage()
{
mIdentifierUpdate = message;
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" LRA:" + getLocationRegistrationArea());
sb.append(" SYS:" + getSystem());
sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID());
sb.append(" CHAN:" + getChannelNumber());
sb.append(" " + SystemService.toString(getSystemServiceClass()));
sb.append(" " + mMessage.toString());
return sb.toString();
}
@Override
public String getLocationRegistrationArea()
{
return mMessage.getHex(LRA, 2);
}
public String getSystem()
{
return mMessage.getHex(SYSTEM_ID, 3);
}
public String getRFSubsystemID()
{
return mMessage.getHex(RFSS_ID, 2);
}
public String getSiteID()
{
return mMessage.getHex(SITE_ID, 2);
}
public int getIdentifier()
{
return mMessage.getInt(IDENTIFIER);
}
public String getChannel()
{
return getIdentifier() + "-" + getChannelNumber();
}
public int getChannelNumber()
{
return mMessage.getInt(CHANNEL);
}
public int getSystemServiceClass()
{
return mMessage.getInt(SYSTEM_SERVICE_CLASS);
}
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
mIdentifierUpdate = message;
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 1 ];
identifiers[ 0 ] = getIdentifier();
return identifiers;
int[] identifiers = new int[1];
identifiers[0] = getIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mIdentifierUpdate, getChannelNumber() );
if(mIdentifierUpdate != null)
{
return mIdentifierUpdate.getDownlinkFrequency(getChannelNumber());
}
return 0;
}
public long getUplinkFrequency()
{
return calculateUplink( mIdentifierUpdate, getChannelNumber() );
if(mIdentifierUpdate != null)
{
return mIdentifierUpdate.getUplinkFrequency(getChannelNumber());
}
return 0;
}
}

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
@@ -5,146 +23,148 @@ import module.decode.p25.message.IdentifierReceiver;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RFSSStatusBroadcastExplicit extends LDU1Message
implements IdentifierReceiver
public class RFSSStatusBroadcastExplicit extends LDU1Message implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( RFSSStatusBroadcastExplicit.class );
public static final int[] LRA = {364, 365, 366, 367, 372, 373, 374, 375};
public static final int[] RECEIVE_IDENTIFIER = {376, 377, 382, 383};
public static final int[] RECEIVE_CHANNEL = {384, 385, 386, 387, 536, 537, 538, 539, 540, 541, 546, 547};
public static final int[] RFSS_ID = {548, 549, 550, 551, 556, 557, 558, 559};
public static final int[] SITE_ID = {560, 561, 566, 567, 568, 569, 570, 571};
public static final int[] TRANSMIT_IDENTIFIER = {720, 721, 722, 723};
public static final int[] TRANSMIT_CHANNEL = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743};
public static final int[] SYSTEM_SERVICE_CLASS = {744, 745, 750, 751, 752, 753, 754, 755};
public static final int[] LRA = { 364,365,366,367,372,373,374,375 };
public static final int[] RECEIVE_IDENTIFIER = { 376,377,382,383 };
public static final int[] RECEIVE_CHANNEL = { 384,385,386,387,536,537,538,
539,540,541,546,547 };
public static final int[] RFSS_ID = { 548,549,550,551,556,557,558,559 };
public static final int[] SITE_ID = { 560,561,566,567,568,569,570,571 };
public static final int[] TRANSMIT_IDENTIFIER = { 720,721,722,723 };
public static final int[] TRANSMIT_CHANNEL = { 724,725,730,731,732,733,734,
735,740,741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS = { 744,745,750,751,752,753,754,755 };
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public RFSSStatusBroadcastExplicit(LDU1Message source)
{
super(source);
}
private IBandIdentifier mTransmitIdentifierUpdate;
private IBandIdentifier mReceiveIdentifierUpdate;
public RFSSStatusBroadcastExplicit( LDU1Message source )
{
super( source );
}
@Override
public String getEventType()
{
return LinkControlOpcode.RFSS_STATUS_BROADCAST_EXPLICIT.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " LRA:" + getLocationRegistrationArea() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " TRANSMIT:" + getTransmitChannelNumber() );
sb.append( " RECEIVE:" + getReceiveChannelNumber() );
sb.append( " " + SystemService.toString( getSystemServiceClass() ) );
sb.append( " " + mMessage.toString() );
return sb.toString();
}
public String getLocationRegistrationArea()
{
return mMessage.getHex( LRA, 2 );
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getTransmitIdentifier()
{
return mMessage.getInt( TRANSMIT_IDENTIFIER );
}
public String getTransmitChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannelNumber();
}
public int getTransmitChannelNumber()
{
return mMessage.getInt( TRANSMIT_CHANNEL );
}
public int getReceiveIdentifier()
{
return mMessage.getInt( RECEIVE_IDENTIFIER );
}
public String getReceiveChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannelNumber();
}
public int getReceiveChannelNumber()
{
return mMessage.getInt( RECEIVE_CHANNEL );
}
public int getSystemServiceClass()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
@Override
public String getMessage()
{
if( identifier == getTransmitIdentifier() )
{
mTransmitIdentifierUpdate = message;
}
if( identifier == getReceiveIdentifier() )
{
mReceiveIdentifierUpdate = message;
}
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" LRA:" + getLocationRegistrationArea());
sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID());
sb.append(" TRANSMIT:" + getTransmitChannelNumber());
sb.append(" RECEIVE:" + getReceiveChannelNumber());
sb.append(" " + SystemService.toString(getSystemServiceClass()));
sb.append(" " + mMessage.toString());
return sb.toString();
}
@Override
public String getLocationRegistrationArea()
{
return mMessage.getHex(LRA, 2);
}
public String getRFSubsystemID()
{
return mMessage.getHex(RFSS_ID, 2);
}
public String getSiteID()
{
return mMessage.getHex(SITE_ID, 2);
}
public int getTransmitIdentifier()
{
return mMessage.getInt(TRANSMIT_IDENTIFIER);
}
public String getTransmitChannel()
{
return getTransmitIdentifier() + "-" + getTransmitChannelNumber();
}
public int getTransmitChannelNumber()
{
return mMessage.getInt(TRANSMIT_CHANNEL);
}
public int getReceiveIdentifier()
{
return mMessage.getInt(RECEIVE_IDENTIFIER);
}
public String getReceiveChannel()
{
return getReceiveIdentifier() + "-" + getReceiveChannelNumber();
}
public int getReceiveChannelNumber()
{
return mMessage.getInt(RECEIVE_CHANNEL);
}
public int getSystemServiceClass()
{
return mMessage.getInt(SYSTEM_SERVICE_CLASS);
}
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
if(identifier == getTransmitIdentifier())
{
mTransmitIdentifierUpdate = message;
}
if(identifier == getReceiveIdentifier())
{
mReceiveIdentifierUpdate = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getTransmitIdentifier();
identifiers[ 1 ] = getReceiveIdentifier();
return identifiers;
int[] identifiers = new int[2];
identifiers[0] = getTransmitIdentifier();
identifiers[1] = getReceiveIdentifier();
return identifiers;
}
public long getDownlinkFrequency()
{
return calculateDownlink( mTransmitIdentifierUpdate, getTransmitChannelNumber() );
if(mTransmitIdentifierUpdate != null)
{
return mTransmitIdentifierUpdate.getDownlinkFrequency(getTransmitChannelNumber());
}
return 0;
}
public long getUplinkFrequency()
{
return calculateUplink( mReceiveIdentifierUpdate, getReceiveChannelNumber() );
if(mReceiveIdentifierUpdate != null)
{
return mReceiveIdentifierUpdate.getUplinkFrequency(getReceiveChannelNumber());
}
return 0;
}
}

View File

@@ -1,3 +1,21 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This 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 module.decode.p25.message.ldu.lc;
import module.decode.p25.message.IBandIdentifier;
@@ -6,151 +24,162 @@ import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.tsbk.osp.control.SystemService;
import module.decode.p25.reference.LinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SecondaryControlChannelBroadcast extends LDU1Message
implements IdentifierReceiver
public class SecondaryControlChannelBroadcast extends LDU1Message implements IdentifierReceiver
{
private final static Logger mLog =
LoggerFactory.getLogger( SecondaryControlChannelBroadcast.class );
public static final int[] RFSS_ID = {364, 365, 366, 367, 372, 373, 374, 375};
public static final int[] SITE_ID = {376, 377, 382, 383, 384, 385, 386, 387};
public static final int[] IDENTIFIER_A = {536, 537, 538, 539};
public static final int[] CHANNEL_A = {540, 541, 546, 547, 548, 549, 550, 551, 556, 557, 558, 559};
public static final int[] SYSTEM_SERVICE_CLASS_A = {560, 561, 566, 567, 568, 569, 570, 571};
public static final int[] IDENTIFIER_B = {720, 721, 722, 723};
public static final int[] CHANNEL_B = {724, 725, 730, 731, 732, 733, 734, 735, 740, 741, 742, 743};
public static final int[] SYSTEM_SERVICE_CLASS_B = {744, 745, 750, 751, 752, 753, 754, 755};
public static final int[] RFSS_ID = { 364,365,366,367,372,373,374,375 };
public static final int[] SITE_ID = { 376,377,382,383,384,385,386,387 };
public static final int[] IDENTIFIER_A = { 536,537,538,539 };
public static final int[] CHANNEL_A = { 540,541,546,547,548,549,550,551,
556,557,558,559 };
public static final int[] SYSTEM_SERVICE_CLASS_A = { 560,561,566,567,568,569,570,571 };
public static final int[] IDENTIFIER_B = { 720,721,722,723 };
public static final int[] CHANNEL_B = { 724,725,730,731,732,733,734,735,740,
741,742,743 };
public static final int[] SYSTEM_SERVICE_CLASS_B = { 744,745,750,751,752,753,754,755 };
private IBandIdentifier mIdentifierProviderA;
private IBandIdentifier mIdentifierProviderB;
public SecondaryControlChannelBroadcast(LDU1Message message)
{
super(message);
}
private IBandIdentifier mIdentifierProviderA;
private IBandIdentifier mIdentifierProviderB;
public SecondaryControlChannelBroadcast( LDU1Message message )
{
super( message );
}
@Override
public String getEventType()
{
return LinkControlOpcode.SECONDARY_CONTROL_CHANNEL_BROADCAST.getDescription();
}
@Override
public String getMessage()
{
StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() );
sb.append( " SITE:" + getRFSubsystemID() + "-" + getSiteID() );
sb.append( " CHAN A:" + getChannelA() );
sb.append( " " + SystemService.toString( getSystemServiceClassA() ) );
sb.append( " CHAN B:" + getChannelB() );
sb.append( " " + SystemService.toString( getSystemServiceClassB() ) );
return sb.toString();
}
public String getRFSubsystemID()
{
return mMessage.getHex( RFSS_ID, 2 );
}
public String getSiteID()
{
return mMessage.getHex( SITE_ID, 2 );
}
public int getIdentifierA()
{
return mMessage.getInt( IDENTIFIER_A );
}
public int getChannelA()
{
return mMessage.getInt( CHANNEL_A );
}
public String getChannelNumberA()
{
return getIdentifierA() + "-" + getChannelA();
}
public int getIdentifierB()
{
return mMessage.getInt( IDENTIFIER_B );
}
public int getChannelB()
{
return mMessage.getInt( CHANNEL_B );
}
public String getChannelNumberB()
{
return getIdentifierB() + "-" + getChannelB();
}
public int getSystemServiceClassA()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS_A );
}
public int getSystemServiceClassB()
{
return mMessage.getInt( SYSTEM_SERVICE_CLASS_B );
}
@Override
public void setIdentifierMessage( int identifier, IBandIdentifier message )
@Override
public String getMessage()
{
if( identifier == getIdentifierA() )
{
mIdentifierProviderA = message;
}
if( identifier == getIdentifierB() )
{
mIdentifierProviderB = message;
}
StringBuilder sb = new StringBuilder();
sb.append(getMessageStub());
sb.append(" SITE:" + getRFSubsystemID() + "-" + getSiteID());
sb.append(" CHAN A:" + getChannelA());
sb.append(" " + SystemService.toString(getSystemServiceClassA()));
sb.append(" CHAN B:" + getChannelB());
sb.append(" " + SystemService.toString(getSystemServiceClassB()));
return sb.toString();
}
@Override
public String getRFSubsystemID()
{
return mMessage.getHex(RFSS_ID, 2);
}
public String getSiteID()
{
return mMessage.getHex(SITE_ID, 2);
}
public int getIdentifierA()
{
return mMessage.getInt(IDENTIFIER_A);
}
public int getChannelA()
{
return mMessage.getInt(CHANNEL_A);
}
public String getChannelNumberA()
{
return getIdentifierA() + "-" + getChannelA();
}
public int getIdentifierB()
{
return mMessage.getInt(IDENTIFIER_B);
}
public int getChannelB()
{
return mMessage.getInt(CHANNEL_B);
}
public String getChannelNumberB()
{
return getIdentifierB() + "-" + getChannelB();
}
public int getSystemServiceClassA()
{
return mMessage.getInt(SYSTEM_SERVICE_CLASS_A);
}
public int getSystemServiceClassB()
{
return mMessage.getInt(SYSTEM_SERVICE_CLASS_B);
}
@Override
public void setIdentifierMessage(int identifier, IBandIdentifier message)
{
if(identifier == getIdentifierA())
{
mIdentifierProviderA = message;
}
if(identifier == getIdentifierB())
{
mIdentifierProviderB = message;
}
}
@Override
public int[] getIdentifiers()
{
int[] identifiers = new int[ 2 ];
identifiers[ 0 ] = getIdentifierA();
identifiers[ 1 ] = getIdentifierB();
return identifiers;
int[] identifiers = new int[2];
identifiers[0] = getIdentifierA();
identifiers[1] = getIdentifierB();
return identifiers;
}
public long getDownlinkFrequencyA()
{
return calculateDownlink( mIdentifierProviderA, getChannelA() );
if(mIdentifierProviderA != null)
{
return mIdentifierProviderA.getDownlinkFrequency(getChannelA());
}
return 0;
}
public long getUplinkFrequencyA()
{
return calculateUplink( mIdentifierProviderA, getChannelA() );
if(mIdentifierProviderA != null)
{
return mIdentifierProviderA.getUplinkFrequency(getChannelA());
}
return 0;
}
public long getDownlinkFrequencyB()
{
return calculateDownlink( mIdentifierProviderB, getChannelB() );
if(mIdentifierProviderB != null)
{
return mIdentifierProviderB.getDownlinkFrequency(getChannelB());
}
return 0;
}
public long getUplinkFrequencyB()
{
return calculateUplink( mIdentifierProviderB, getChannelB() );
if(mIdentifierProviderB != null)
{
return mIdentifierProviderB.getUplinkFrequency(getChannelB());
}
return 0;
}
}

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