#6 DMR Decoder (#947)

* #6 DMR decoder feature - work in progress

* #6 DMR Decoder.

* #6 DMR Decoder.  Code review updates.

Co-authored-by: Zhenyu Mao <maozhenyu@foxmail.com>
Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-10-12 04:57:59 -04:00
committed by GitHub
parent b061db1189
commit 7eec82deee
436 changed files with 36258 additions and 2409 deletions

View File

@@ -390,6 +390,8 @@ public class Channel extends Configuration implements Listener<SourceEvent>
sb.append(hasSite() ? getSite() : "SITE");
sb.append("_");
sb.append(getName());
sb.append("_");
sb.append(getChannelID());
return sb.toString();
}

View File

@@ -0,0 +1,47 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.controller.channel;
import io.github.dsheirer.module.ModuleEventBusMessage;
/**
* Notification that a channel configuration has changed.
*/
public class ChannelConfigurationChangeNotification extends ModuleEventBusMessage
{
private Channel mChannel;
/**
* Constructs an instance
* @param channel current
*/
public ChannelConfigurationChangeNotification(Channel channel)
{
mChannel = channel;
}
/**
* New channel configuration
*/
public Channel getChannel()
{
return mChannel;
}
}

View File

@@ -0,0 +1,59 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.controller.channel;
import io.github.dsheirer.module.ModuleEventBusMessage;
/**
* Request to convert a currently processing standard channel type to a traffic channel type. This request will
* normally be handled by the ChannelProcessingManager instance.
*/
public class ChannelConversionRequest extends ModuleEventBusMessage
{
private Channel mCurrentChannel;
private Channel mTrafficChannel;
/**
* Constructs an instance.
* @param currentChannel that is actively processing
* @param trafficChannel to convert to
*/
public ChannelConversionRequest(Channel currentChannel, Channel trafficChannel)
{
mCurrentChannel = currentChannel;
mTrafficChannel = trafficChannel;
}
/**
* Channel configuration for the channel that is currently processing
*/
public Channel getCurrentChannel()
{
return mCurrentChannel;
}
/**
* Channel configuration for the traffic channel to convert to
*/
public Channel getTrafficChannel()
{
return mTrafficChannel;
}
}

View File

@@ -1,64 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* *****************************************************************************
*/
package io.github.dsheirer.controller.channel;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.identifier.IdentifierCollection;
/**
* Channel grant event with a channel descriptor that identifies the channel that is being created
*/
public class ChannelGrantEvent extends ChannelEvent
{
private IChannelDescriptor mChannelDescriptor;
private IdentifierCollection mIdentifierCollection;
/**
* Constructs a channel grant event
* @param channel with setup/configuration details
* @param event to convey for the channel
* @param channelDescriptor for the channel that is to be created
* @param identifierCollection containing identifiers to preload into the channel
*/
public ChannelGrantEvent(Channel channel, Event event, IChannelDescriptor channelDescriptor,
IdentifierCollection identifierCollection)
{
super(channel, event);
mChannelDescriptor = channelDescriptor;
mIdentifierCollection = identifierCollection;
}
/**
* Channel descriptor that contains uplink and downlink information
*/
public IChannelDescriptor getChannelDescriptor()
{
return mChannelDescriptor;
}
/**
* Identifier collection to use in preloading the channel state for the allocated channel
*/
public IdentifierCollection getIdentifierCollection()
{
return mIdentifierCollection;
}
}

View File

@@ -21,25 +21,24 @@
*/
package io.github.dsheirer.controller.channel;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.AudioSegment;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.channel.metadata.ChannelAndMetadata;
import io.github.dsheirer.channel.metadata.ChannelMetadata;
import io.github.dsheirer.channel.metadata.ChannelMetadataModel;
import io.github.dsheirer.controller.channel.event.ChannelStartProcessingRequest;
import io.github.dsheirer.controller.channel.event.PreloadDataContent;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.filter.FilterSet;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.module.Module;
import io.github.dsheirer.module.ProcessingChain;
import io.github.dsheirer.module.decode.DecoderFactory;
import io.github.dsheirer.module.decode.event.IDecodeEvent;
import io.github.dsheirer.module.decode.event.MessageActivityModel;
import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.RecorderFactory;
@@ -51,6 +50,7 @@ import io.github.dsheirer.source.SourceException;
import io.github.dsheirer.source.SourceManager;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.util.ThreadPool;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,6 +60,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Channel processing manager handles all starting and stopping of channel decoding. A processing chain is created
@@ -83,7 +85,8 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
private SourceManager mSourceManager;
private AliasModel mAliasModel;
private UserPreferences mUserPreferences;
private List<Long> mUnTunableFrequencies = new ArrayList<>();
private List<Long> mLoggedFrequencies = new ArrayList<>();
private List<ScheduledFuture<?>> mDelayedChannelStartTasks = new ArrayList<>();
/**
* Constructs the channel processing manager
@@ -167,6 +170,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
*
* @param event that requests either enable/start or disable/stop a channel.
*/
@Deprecated //TODO: convert all usage to start(ChannelStartProcessingRequest request) method
@Override
public synchronized void receive(ChannelEvent event)
{
@@ -179,7 +183,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
{
try
{
startProcessing(event);
startProcessing(new ChannelStartProcessingRequest(event.getChannel()));
}
catch(ChannelException ce)
{
@@ -187,9 +191,9 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
{
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
if(!mUnTunableFrequencies.contains(frequency))
if(!mLoggedFrequencies.contains(frequency))
{
mUnTunableFrequencies.add(frequency);
mLoggedFrequencies.add(frequency);
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequency +
"] - " + ce.getMessage());
}
@@ -199,9 +203,9 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel
.getSourceConfiguration()).getFrequencies();
if(frequencies.size() > 0 && !mUnTunableFrequencies.contains(frequencies.get(0)))
if(frequencies.size() > 0 && !mLoggedFrequencies.contains(frequencies.get(0)))
{
mUnTunableFrequencies.add(frequencies.get(0));
mLoggedFrequencies.add(frequencies.get(0));
mLog.error("Error starting requested channel [" + channel.getName() + ":" + frequencies +
"] - " + ce.getMessage());
}
@@ -214,40 +218,16 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
}
break;
case REQUEST_DISABLE:
if(channel.isProcessing())
{
try
{
switch(channel.getChannelType())
{
case STANDARD:
stopProcessing(channel, true);
break;
case TRAFFIC:
//Don't remove traffic channel processing chains
//until explicitly deleted, so that we can reuse them
stopProcessing(channel, false);
break;
default:
break;
}
}
catch(ChannelException ce)
{
mLog.error("Error stopping channel [" + channel.getName() + "] - " + ce.getMessage());
}
}
break;
case NOTIFICATION_DELETE:
if(channel.isProcessing())
{
try
{
stopProcessing(channel, true);
stopProcessing(channel);
}
catch(ChannelException ce)
{
mLog.error("Error stopping deleted channel [" + channel.getName() + "] - " + ce.getMessage());
mLog.error("Error stopping channel [" + channel.getName() + "] - " + ce.getMessage());
}
}
break;
@@ -263,7 +243,33 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
*/
public void start(Channel channel) throws ChannelException
{
startProcessing(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE));
startProcessing(new ChannelStartProcessingRequest(channel));
}
/**
* Request to start processing a channel received over the Guava event bus.
*
* Note: since this is received over the event bus, we handle any channel exceptions inside this method.
*/
@Subscribe
public void startChannelRequest(ChannelStartProcessingRequest request)
{
if(!isProcessing(request.getChannel()))
{
try
{
startProcessing(request);
}
catch(ChannelException ce)
{
if(request.isPersistentAttempt())
{
ScheduledFuture<?> future = ThreadPool.SCHEDULED
.schedule(new DelayedChannelStartTask(request), 500, TimeUnit.MILLISECONDS);
mDelayedChannelStartTasks.add(future);
}
}
}
}
/**
@@ -272,24 +278,21 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
*/
public void stop(Channel channel) throws ChannelException
{
stopProcessing(channel, !channel.isTrafficChannel());
stopProcessing(channel);
}
/**
* Starts a channel/processing chain
*
* @param event that requested the channel start
* Starts a channel processing
* @param request containing channel and other details
* @throws ChannelException if a source is not available for the channel
*/
private void startProcessing(ChannelEvent event) throws ChannelException
private void startProcessing(ChannelStartProcessingRequest request) throws ChannelException
{
Channel channel = event.getChannel();
Channel channel = request.getChannel();
ProcessingChain processingChain = mProcessingChains.get(channel);
//If we're already processing, ignore the request
if(processingChain != null && processingChain.isProcessing())
if(isProcessing(channel))
{
throw new ChannelException("Channel is already playing");
return;
}
//Ensure that we can get a source before we construct a new processing chain
@@ -316,74 +319,90 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
throw new ChannelException("No Tuner Available");
}
if(processingChain == null)
ProcessingChain processingChain = new ProcessingChain(channel, mAliasModel);
//Certain decoders aggregate the decode events in the parent channel that also includes any events produced
//by the traffic channels. Establish listener registration depending on if this channel is a traffic channel
//and the request contains the parent event history, or if this is a parent channel and the request contains
//the traffic channel event history.
if(request.hasParentDecodeEventHistory())
{
processingChain = new ProcessingChain(channel, mAliasModel);
mChannelEventBroadcaster.addListener(processingChain);
/* Register global listeners */
for(Listener<AudioSegment> listener : mAudioSegmentListeners)
{
processingChain.addAudioSegmentListener(listener);
}
for(Listener<IDecodeEvent> listener : mDecodeEventListeners)
{
processingChain.addDecodeEventListener(listener);
}
//Add a listener to detect source error state that indicates the channel should be shutdown
processingChain.addSourceEventListener(sourceEvent ->
{
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_ERROR_STATE && sourceEvent.getSource() != null)
{
Channel toShutdown = null;
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChains.entrySet())
{
if(entry.getValue().hasSource(sourceEvent.getSource()))
{
toShutdown = entry.getKey();
break;
}
}
if(toShutdown != null)
{
mLog.warn("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
try
{
stopProcessing(toShutdown, true);
}
catch(ChannelException ce)
{
mLog.error("Error stopping channel [" + channel.getName() + "] with source error - " +
ce.getMessage());
}
}
}
});
//Register this manager to receive channel events from traffic channel manager modules within
//the processing chain
processingChain.addChannelEventListener(this);
//Register channel to receive frequency correction events to show in the spectral display (hack!)
processingChain.addFrequencyChangeListener(channel);
/* Processing Modules */
List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences);
processingChain.addModules(modules);
/* Setup message activity model with filtering */
FilterSet<IMessage> messageFilter = DecoderFactory.getMessageFilters(modules);
MessageActivityModel messageModel = new MessageActivityModel(messageFilter);
processingChain.setMessageActivityModel(messageModel);
processingChain.getDecodeEventHistory().addListener(request.getParentDecodeEventHistory());
}
else if(request.hasChildDecodeEventHistory())
{
request.getChildDecodeEventHistory().addListener(processingChain.getDecodeEventHistory());
}
/* Setup event logging */
//Register to receive event bus requests/notifications
processingChain.getEventBus().register(ChannelProcessingManager.this);
mChannelEventBroadcaster.addListener(processingChain);
/* Register global listeners */
for(Listener<AudioSegment> listener : mAudioSegmentListeners)
{
processingChain.addAudioSegmentListener(listener);
}
for(Listener<IDecodeEvent> listener : mDecodeEventListeners)
{
processingChain.addDecodeEventListener(listener);
}
//Add a listener to detect source error state that indicates the channel should be shutdown
processingChain.addSourceEventListener(sourceEvent ->
{
if(sourceEvent.getEvent() == SourceEvent.Event.NOTIFICATION_ERROR_STATE && sourceEvent.getSource() != null)
{
Channel toShutdown = null;
for(Map.Entry<Channel,ProcessingChain> entry: mProcessingChains.entrySet())
{
if(entry.getValue().hasSource(sourceEvent.getSource()))
{
toShutdown = entry.getKey();
break;
}
}
if(toShutdown != null)
{
mLog.warn("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
try
{
stopProcessing(toShutdown);
}
catch(ChannelException ce)
{
mLog.error("Error stopping channel [" + channel.getName() + "] with source error - " +
ce.getMessage());
}
}
}
});
//Register this manager to receive channel events from traffic channel manager modules within
//the processing chain
processingChain.addChannelEventListener(this);
//Register channel to receive frequency correction events to show in the spectral display (hack!)
processingChain.addFrequencyChangeListener(channel);
/* Processing Modules */
List<Module> modules = DecoderFactory.getModules(mChannelMapModel, channel, mAliasModel, mUserPreferences,
request.getTrafficChannelManager());
processingChain.addModules(modules);
//Post preload data from the request to the event bus. Modules that can handle preload data will annotate
//their processor method with @Subscribe to receive each specific preload data content class.
for(PreloadDataContent preloadDataContent: request.getPreloadDataContents())
{
processingChain.getEventBus().post(preloadDataContent);
}
//Setup event logging
List<Module> loggers = mEventLogManager.getLoggers(channel);
if(!loggers.isEmpty())
@@ -398,25 +417,22 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
processingChain.setSource(source);
//Inject the channel identifier for traffic channels and preload user identifiers
if(channel.isTrafficChannel() && event instanceof ChannelGrantEvent)
if(channel.isTrafficChannel())
{
ChannelGrantEvent channelGrantEvent = (ChannelGrantEvent)event;
IChannelDescriptor channelDescriptor = channelGrantEvent.getChannelDescriptor();
IdentifierCollection identifierCollection = channelGrantEvent.getIdentifierCollection();
if(channelDescriptor != null)
if(request.hasChannelDescriptor() && request.hasIdentifierCollection())
{
for(int timeslot = 0; timeslot < channelDescriptor.getTimeslotCount(); timeslot++)
for(int timeslot = 0; timeslot < request.getChannelDescriptor().getTimeslotCount(); timeslot++)
{
DecoderLogicalChannelNameIdentifier identifier =
DecoderLogicalChannelNameIdentifier.create(channelDescriptor.toString(), channelDescriptor.getProtocol());
DecoderLogicalChannelNameIdentifier.create(request.getChannelDescriptor().toString(),
request.getChannelDescriptor().getProtocol());
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(identifier,
IdentifierUpdateNotification.Operation.ADD, timeslot);
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
//Inject scramble parameters
for(Identifier scrambleParameters: identifierCollection.getIdentifiers(Form.SCRAMBLE_PARAMETERS))
for(Identifier scrambleParameters: request.getIdentifierCollection()
.getIdentifiers(Form.SCRAMBLE_PARAMETERS))
{
//Broadcast scramble parameters to both timeslots
IdentifierUpdateNotification scrambleNotification = new IdentifierUpdateNotification(scrambleParameters,
@@ -426,13 +442,13 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
}
}
for(Identifier userIdentifier : identifierCollection.getIdentifiers(IdentifierClass.USER))
for(Identifier userIdentifier : request.getIdentifierCollection().getIdentifiers(IdentifierClass.USER))
{
if(channelDescriptor.getTimeslotCount() > 1)
if(request.getChannelDescriptor().getTimeslotCount() > 1)
{
//Only broadcast an identifier update for the timeslot specified in the originating collection
IdentifierUpdateNotification notification = new IdentifierUpdateNotification(userIdentifier,
IdentifierUpdateNotification.Operation.ADD, channelGrantEvent.getIdentifierCollection().getTimeslot());
IdentifierUpdateNotification.Operation.ADD, request.getIdentifierCollection().getTimeslot());
processingChain.getChannelState().updateChannelStateIdentifiers(notification);
}
else
@@ -446,13 +462,13 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
}
mProcessingChains.put(channel, processingChain);
processingChain.start();
//This has to be done on the FX event thread when the playlist editor is constructed
Platform.runLater(() -> channel.setProcessing(true));
getChannelMetadataModel().add(processingChain.getChannelState().getChannelMetadata(), channel);
mProcessingChains.put(channel, processingChain);
getChannelMetadataModel().add(new ChannelAndMetadata(channel, processingChain.getChannelState().getChannelMetadata()));
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_START));
}
@@ -461,16 +477,15 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
* Stops the channel/processing chain.
*
* @param channel to stop
* @param remove set to true to remove the associated processing chain.
*/
private void stopProcessing(Channel channel, boolean remove) throws ChannelException
private void stopProcessing(Channel channel) throws ChannelException
{
//This has to be done on the FX event thread when the playlist editor is constructed
Platform.runLater(() -> channel.setProcessing(false));
if(mProcessingChains.containsKey(channel))
{
ProcessingChain processingChain = mProcessingChains.get(channel);
ProcessingChain processingChain = mProcessingChains.remove(channel);
for(ChannelMetadata channelMetadata: processingChain.getChannelState().getChannelMetadata())
{
@@ -480,7 +495,6 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
processingChain.stop();
processingChain.removeEventLoggingModules();
processingChain.removeRecordingModules();
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
@@ -488,13 +502,11 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
channel.resetFrequencyCorrection();
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
mChannelEventBroadcaster.removeListener(processingChain);
if(remove)
{
mChannelEventBroadcaster.removeListener(processingChain);
mProcessingChains.remove(channel);
processingChain.dispose();
}
//Unregister for event bus requests and notifications
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
processingChain.dispose();
}
else
{
@@ -507,13 +519,21 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
*/
public void shutdown()
{
List<ScheduledFuture<?>> delayedTasks = new ArrayList<>(mDelayedChannelStartTasks);
for(ScheduledFuture<?> delayedTask: delayedTasks)
{
delayedTask.cancel(true);
mDelayedChannelStartTasks.remove(delayedTask);
}
List<Channel> channelsToStop = new ArrayList<>(mProcessingChains.keySet());
for(Channel channel : channelsToStop)
{
try
{
stopProcessing(channel, true);
stopProcessing(channel);
}
catch(ChannelException ce)
{
@@ -522,6 +542,41 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
}
}
/**
* Process a request to convert a currently processing standard channel type to a traffic channel type.
* @param request from the currently processing channel's processing chain event bus
*/
@Subscribe
public void convertToTrafficChannel(ChannelConversionRequest request)
{
//Update the channel to processing chain map.
ProcessingChain processingChain = mProcessingChains.remove(request.getCurrentChannel());
if(processingChain != null)
{
//Remove the traffic channel manager from this processing chain. Reuse or reinsertion of the traffic
//channel manager to another processing chain is handled separately.
processingChain.removeTrafficChannelManager();
//Update processing flag for each configuration.
Platform.runLater(() -> {
request.getCurrentChannel().setProcessing(false);
request.getTrafficChannel().setProcessing(true);
});
mProcessingChains.put(request.getTrafficChannel(), processingChain);
mChannelMetadataModel.updateChannelMetadataToChannelMap(processingChain.getChannelState().getChannelMetadata(),
request.getTrafficChannel());
//Post a change notification so that processing chain modules can reconfigure
processingChain.channelConfigurationChanged(new ChannelConfigurationChangeNotification(request.getTrafficChannel()));
}
else
{
mLog.warn("Request to convert to traffic channel ignored - no processing chain was found");
}
}
/**
* Adds a message listener that will be added to all channels to receive
* any messages.
@@ -571,4 +626,31 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
{
mChannelEventBroadcaster.removeListener(listener);
}
/**
* Task to scheduling attempt to start a channel after previous attempts failed for lack of tuner channel
*/
public class DelayedChannelStartTask implements Runnable
{
private ChannelStartProcessingRequest mRequest;
public DelayedChannelStartTask(ChannelStartProcessingRequest request)
{
mRequest = request;
}
@Override
public void run()
{
try
{
mDelayedChannelStartTasks.remove(this);
startChannelRequest(mRequest);
}
catch(Throwable t)
{
mLog.error("Error executing persistent channel start task");
}
}
}
}

View File

@@ -0,0 +1,228 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.controller.channel.event;
import io.github.dsheirer.channel.IChannelDescriptor;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.module.ModuleEventBusMessage;
import io.github.dsheirer.module.decode.event.DecodeEventHistory;
import io.github.dsheirer.module.decode.traffic.TrafficChannelManager;
import java.util.ArrayList;
import java.util.List;
/**
* Request to start/enable processing for the specified channel
*/
public class ChannelStartProcessingRequest extends ModuleEventBusMessage
{
private final Channel mChannel;
private final IChannelDescriptor mChannelDescriptor;
private final IdentifierCollection mIdentifierCollection;
private final TrafficChannelManager mTrafficChannelManager;
private final List<PreloadDataContent<?>> mPreloadDataContents = new ArrayList<>();
private DecodeEventHistory mParentDecodeEventHistory;
private DecodeEventHistory mChildDecodeEventHistory;
private boolean mPersistentAttempt;
/**
* Constructs an instance
* @param channel to start
* @param channelDescriptor that identifies which channel in a multi-channel config to start
* @param identifierCollection to use for the new channel
* @param trafficChannelManager (optional) to use for the new channel
*/
public ChannelStartProcessingRequest(Channel channel, IChannelDescriptor channelDescriptor,
IdentifierCollection identifierCollection,
TrafficChannelManager trafficChannelManager)
{
mChannel = channel;
mChannelDescriptor = channelDescriptor;
mIdentifierCollection = identifierCollection;
mTrafficChannelManager = trafficChannelManager;
}
/**
* Constructs an instance
* @param channel to start
* @param channelDescriptor that identifies which channel in a multi-channel config to start
* @param identifierCollection to use for the new channel
*/
public ChannelStartProcessingRequest(Channel channel, IChannelDescriptor channelDescriptor,
IdentifierCollection identifierCollection)
{
this(channel, channelDescriptor, identifierCollection, null);
}
/**
* Constructs an instance
* @param channel to start processing
* @param trafficChannelManager optional to use in the processing chain
*/
public ChannelStartProcessingRequest(Channel channel, TrafficChannelManager trafficChannelManager)
{
this(channel, null, null, trafficChannelManager);
}
/**
* Constructs an instance
* @param channel to start processing
*/
public ChannelStartProcessingRequest(Channel channel)
{
this(channel, null, null, null);
}
/**
* Sets a flag to persistently attempt to start this channel. When set to true, the channel processing manager
* will repeatedly try to (re)start this channel while the application continues to run.
* @param persistentAttempt if this start request should be continuously attempted until successful.
*/
public void setPersistentAttempt(boolean persistentAttempt)
{
mPersistentAttempt = persistentAttempt;
}
/**
* Indicates if this request should be persistently attempted until the channel processing is started.
*/
public boolean isPersistentAttempt()
{
return mPersistentAttempt;
}
/**
* Channel to start processing
* @return channel to start
*/
public Channel getChannel()
{
return mChannel;
}
/**
* Optional traffic channel manager to (re)use.
* @return traffic channel manager or null
*/
public TrafficChannelManager getTrafficChannelManager()
{
return mTrafficChannelManager;
}
/**
* Optional parent decode event history module to be added as a decode event listener
*/
public DecodeEventHistory getParentDecodeEventHistory()
{
return mParentDecodeEventHistory;
}
/**
* Indicates if this request has a parent decode event history
*/
public boolean hasParentDecodeEventHistory()
{
return mParentDecodeEventHistory != null;
}
/**
* Sets the parent decode event history
* @param parentHistory
*/
public void setParentDecodeEventHistory(DecodeEventHistory parentHistory)
{
mParentDecodeEventHistory = parentHistory;
}
/**
* Optional child decode event history module
*/
public DecodeEventHistory getChildDecodeEventHistory()
{
return mChildDecodeEventHistory;
}
/**
* Indicates if this request has a child decode event history
*/
public boolean hasChildDecodeEventHistory()
{
return mChildDecodeEventHistory != null;
}
/**
* Sets the child decode event history
* @param childHistory
*/
public void setChildDecodeEventHistory(DecodeEventHistory childHistory)
{
mChildDecodeEventHistory = childHistory;
}
/**
* Channel descriptor to use for the started channel
*/
public IChannelDescriptor getChannelDescriptor()
{
return mChannelDescriptor;
}
/**
* Indicates if this request has a non-null channel descriptor
*/
public boolean hasChannelDescriptor()
{
return mChannelDescriptor != null;
}
/**
* Identifier collection to use for preloading the channel with identifiers
*/
public IdentifierCollection getIdentifierCollection()
{
return mIdentifierCollection;
}
/**
* Indicates if this request has a non-null identifier collection
*/
public boolean hasIdentifierCollection()
{
return mIdentifierCollection != null;
}
/**
* Adds data content to be preloaded onto the event bus at startup.
* @param content to broadcast.
*/
public void addPreloadDataContent(PreloadDataContent<?> content)
{
mPreloadDataContents.add(content);
}
/**
* Data content that should be broadcast on the processing chain event bus at startup.
*/
public List<PreloadDataContent<?>> getPreloadDataContents()
{
return mPreloadDataContents;
}
}

View File

@@ -0,0 +1,58 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.controller.channel.event;
import io.github.dsheirer.module.ModuleEventBusMessage;
/**
* Data that should be preloaded into a processing chain by broadcasting the data onto the processing chain so that
* any module can subscribe to it.
* @param <T> data generic that should be loaded into modules in the processing chain.
*/
public abstract class PreloadDataContent<T> extends ModuleEventBusMessage
{
private final T mData;
/**
* Constructs an instance
* @param data to preload
*/
public PreloadDataContent(T data)
{
mData = data;
}
/**
* Data to preload
* @return
*/
public T getData()
{
return mData;
}
/**
* Indicates if the data payload is non-null
*/
public boolean hasData()
{
return mData != null;
}
}