mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 01:27:34 -05:00
P25 Phase 2 Decoder implementation.
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.ChannelEvent;
|
||||
import io.github.dsheirer.controller.channel.IChannelEventProvider;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
|
||||
import io.github.dsheirer.sample.IOverflowListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventProvider;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
||||
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class AbstractChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider,
|
||||
IDecoderStateEventProvider, ISourceEventProvider, IHeartbeatListener, ISquelchStateProvider,
|
||||
IdentifierUpdateProvider, IOverflowListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractChannelState.class);
|
||||
|
||||
protected Listener<ChannelEvent> mChannelEventListener;
|
||||
protected Listener<IDecodeEvent> mDecodeEventListener;
|
||||
protected Listener<DecoderStateEvent> mDecoderStateListener;
|
||||
protected Listener<SourceEvent> mExternalSourceEventListener;
|
||||
protected Channel mChannel;
|
||||
protected boolean mSourceOverflow = false;
|
||||
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
|
||||
|
||||
public AbstractChannelState(Channel channel)
|
||||
{
|
||||
mChannel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked each time that a heartbeat is received so that sub-class implementations can check current timers and
|
||||
* adjust channel state as necessary. The heartbeat arrives on a periodic basis independent of any decoded
|
||||
* messages so that channel state is not entirely dependent on a continuous decoded message stream.
|
||||
*/
|
||||
protected abstract void checkState();
|
||||
|
||||
public abstract Collection<ChannelMetadata> getChannelMetadata();
|
||||
|
||||
public abstract void updateChannelStateIdentifiers(IdentifierUpdateNotification notification);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this channel's sample buffer is in overflow state, meaning that the inbound sample
|
||||
* stream is not being processed fast enough and samples are being thrown away until the processing can
|
||||
* catch up.
|
||||
*
|
||||
* @return true if the channel is in overflow state.
|
||||
*/
|
||||
public boolean isOverflow()
|
||||
{
|
||||
return mSourceOverflow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChannelEventListener(Listener<ChannelEvent> listener)
|
||||
{
|
||||
mChannelEventListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChannelEventListener()
|
||||
{
|
||||
mChannelEventListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a decoder state event listener
|
||||
*/
|
||||
@Override
|
||||
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
|
||||
{
|
||||
mDecoderStateListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the decoder state event listener
|
||||
*/
|
||||
@Override
|
||||
public void removeDecoderStateListener()
|
||||
{
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive source events from the channel state
|
||||
*/
|
||||
@Override
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mExternalSourceEventListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* De-Registers a listener from receiving source events from the channel state
|
||||
*/
|
||||
@Override
|
||||
public void removeSourceEventListener()
|
||||
{
|
||||
mExternalSourceEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
|
||||
* functions.
|
||||
*
|
||||
* 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 HeartbeatReceiver implements Listener<Heartbeat>
|
||||
{
|
||||
@Override
|
||||
public void receive(Heartbeat heartbeat)
|
||||
{
|
||||
checkState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.IMessageListener;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.event.ActivitySummaryProvider;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AbstractDecoderState extends Module implements ActivitySummaryProvider, Listener<IMessage>,
|
||||
IDecodeEventProvider, IDecoderStateEventListener, IDecoderStateEventProvider, IMessageListener,
|
||||
IdentifierUpdateProvider, IdentifierUpdateListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractDecoderState.class);
|
||||
protected String DIVIDER1 = "======================================================\n";
|
||||
protected String DIVIDER2 = "------------------------------------------------------\n";
|
||||
/* This has to be a broadcaster in order for references to persist */
|
||||
protected Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
|
||||
protected Listener<DecoderStateEvent> mDecoderStateListener;
|
||||
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
|
||||
|
||||
public abstract DecoderType getDecoderType();
|
||||
|
||||
/**
|
||||
* Provides subclass reference to the decode event broadcaster
|
||||
*/
|
||||
protected Broadcaster<IDecodeEvent> getDecodeEventBroadcaster()
|
||||
{
|
||||
return mDecodeEventBroadcaster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<IMessage> getMessageListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IDecoderStateEventListener interface to receive state
|
||||
* reset events.
|
||||
*/
|
||||
public abstract void receiveDecoderStateEvent(DecoderStateEvent event);
|
||||
|
||||
/**
|
||||
* Activity Summary - textual summary of activity observed by the channel state.
|
||||
*/
|
||||
public abstract String getActivitySummary();
|
||||
|
||||
/**
|
||||
* Broadcasts a decode event to any registered listeners
|
||||
*/
|
||||
protected void broadcast(IDecodeEvent event)
|
||||
{
|
||||
mDecodeEventBroadcaster.broadcast(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a call event listener
|
||||
*/
|
||||
@Override
|
||||
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the call event listener
|
||||
*/
|
||||
@Override
|
||||
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<DecoderStateEvent> getDecoderStateListener()
|
||||
{
|
||||
return mDecoderStateEventListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a channel state event to any registered listeners
|
||||
*/
|
||||
protected void broadcast(DecoderStateEvent event)
|
||||
{
|
||||
if(mDecoderStateListener != null)
|
||||
{
|
||||
mDecoderStateListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a decoder state event listener
|
||||
*/
|
||||
@Override
|
||||
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
|
||||
{
|
||||
mDecoderStateListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the decoder state event listener
|
||||
*/
|
||||
@Override
|
||||
public void removeDecoderStateListener()
|
||||
{
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(DecoderStateEvent event)
|
||||
{
|
||||
receiveDecoderStateEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* 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.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
@@ -48,11 +50,6 @@ public class AlwaysUnsquelchedDecoderState extends DecoderState
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActivitySummary()
|
||||
{
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
@@ -7,23 +29,28 @@ import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
*/
|
||||
public class ChangeChannelTimeoutEvent extends DecoderStateEvent
|
||||
{
|
||||
private ChannelType mChannelType;
|
||||
private long mCallTimeout;
|
||||
|
||||
public ChangeChannelTimeoutEvent( Object source, ChannelType channelType, long timeout )
|
||||
{
|
||||
super( source, Event.CHANGE_CALL_TIMEOUT, State.IDLE );
|
||||
mChannelType = channelType;
|
||||
mCallTimeout = timeout;
|
||||
}
|
||||
|
||||
public ChannelType getChannelType()
|
||||
{
|
||||
return mChannelType;
|
||||
}
|
||||
|
||||
public long getCallTimeout()
|
||||
{
|
||||
return mCallTimeout;
|
||||
}
|
||||
private ChannelType mChannelType;
|
||||
private long mCallTimeout;
|
||||
|
||||
public ChangeChannelTimeoutEvent(Object source, ChannelType channelType, long timeout, int timeslot)
|
||||
{
|
||||
super(source, Event.CHANGE_CALL_TIMEOUT, State.IDLE, timeslot);
|
||||
mChannelType = channelType;
|
||||
mCallTimeout = timeout;
|
||||
}
|
||||
|
||||
public ChangeChannelTimeoutEvent(Object source, ChannelType channelType, long timeout)
|
||||
{
|
||||
this(source, channelType, timeout, 0);
|
||||
}
|
||||
|
||||
public ChannelType getChannelType()
|
||||
{
|
||||
return mChannelType;
|
||||
}
|
||||
|
||||
public long getCallTimeout()
|
||||
{
|
||||
return mCallTimeout;
|
||||
}
|
||||
}
|
||||
@@ -1,762 +0,0 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
|
||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
import io.github.dsheirer.controller.channel.ChannelEvent;
|
||||
import io.github.dsheirer.controller.channel.IChannelEventProvider;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
|
||||
import io.github.dsheirer.sample.IOverflowListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.ISourceEventProvider;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceType;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
||||
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChannelState extends Module implements IChannelEventProvider, IDecodeEventProvider,
|
||||
IDecoderStateEventListener, IDecoderStateEventProvider, ISourceEventListener, ISourceEventProvider,
|
||||
IHeartbeatListener, ISquelchStateProvider, IOverflowListener, IdentifierUpdateListener, IdentifierUpdateProvider
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
|
||||
|
||||
public static final long FADE_TIMEOUT_DELAY = 1200;
|
||||
public static final long RESET_TIMEOUT_DELAY = 2000;
|
||||
|
||||
private State mState = State.IDLE;
|
||||
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||
private Listener<ChannelEvent> mChannelEventListener;
|
||||
private Listener<IDecodeEvent> mDecodeEventListener;
|
||||
private Listener<DecoderStateEvent> mDecoderStateListener;
|
||||
private Listener<SquelchState> mSquelchStateListener;
|
||||
private Listener<SourceEvent> mExternalSourceEventListener;
|
||||
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
|
||||
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
|
||||
private Channel mChannel;
|
||||
private SourceEventListener mInternalSourceEventListener;
|
||||
private ChannelMetadata mChannelMetadata;
|
||||
private IdentifierUpdateNotificationProxy mIdentifierUpdateNotificationProxy = new IdentifierUpdateNotificationProxy();
|
||||
|
||||
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;
|
||||
private long mFadeTimeout;
|
||||
private long mEndTimeout;
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
public ChannelState(Channel channel, AliasModel aliasModel)
|
||||
{
|
||||
mChannel = channel;
|
||||
mChannelMetadata = new ChannelMetadata(aliasModel);
|
||||
mIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
|
||||
createConfigurationIdentifiers(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates configuration identifiers for the channel name, system, site and alias list name.
|
||||
*/
|
||||
private void createConfigurationIdentifiers(Channel channel)
|
||||
{
|
||||
mIdentifierCollection.update(DecoderTypeConfigurationIdentifier.create(channel.getDecodeConfiguration().getDecoderType()));
|
||||
|
||||
if(channel.hasSystem())
|
||||
{
|
||||
mIdentifierCollection.update(SystemConfigurationIdentifier.create(channel.getSystem()));
|
||||
}
|
||||
if(channel.hasSite())
|
||||
{
|
||||
mIdentifierCollection.update(SiteConfigurationIdentifier.create(channel.getSite()));
|
||||
}
|
||||
if(channel.getName() != null && !channel.getName().isEmpty())
|
||||
{
|
||||
mIdentifierCollection.update(ChannelNameConfigurationIdentifier.create(channel.getName()));
|
||||
}
|
||||
if(channel.getAliasListName() != null && !channel.getAliasListName().isEmpty())
|
||||
{
|
||||
mIdentifierCollection.update(AliasListConfigurationIdentifier.create(channel.getAliasListName()));
|
||||
}
|
||||
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
||||
{
|
||||
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
|
||||
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequency));
|
||||
}
|
||||
else if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
||||
{
|
||||
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencies();
|
||||
|
||||
if(frequencies.size() > 0)
|
||||
{
|
||||
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequencies.get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to receive channel identifier updates from this channel state and from any
|
||||
* decoder states.
|
||||
*/
|
||||
@Override
|
||||
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
|
||||
{
|
||||
return mChannelMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel state identifier collection using the update notification. This update will be reflected
|
||||
* in the internal channel state and will also be broadcast to any listeners, including the channel metadata for
|
||||
* this channel state.
|
||||
*/
|
||||
public void updateChannelStateIdentifiers(IdentifierUpdateNotification notification)
|
||||
{
|
||||
mIdentifierCollection.receive(notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateNotificationProxy.setListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeIdentifierUpdateListener()
|
||||
{
|
||||
mIdentifierUpdateNotificationProxy.removeListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel metadata for this channel.
|
||||
*/
|
||||
public ChannelMetadata getChannelMetadata()
|
||||
{
|
||||
return mChannelMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this channel state and prepares it for reuse.
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mState = State.IDLE;
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
|
||||
mIdentifierCollection.remove(IdentifierClass.USER);
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
|
||||
mSourceOverflow = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
mIdentifierCollection.broadcastIdentifiers();
|
||||
|
||||
if(mChannel.getChannelType() == ChannelType.TRAFFIC)
|
||||
{
|
||||
setState(State.CALL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
processTeardownState();
|
||||
mSquelchLocked = false;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mDecodeEventListener = null;
|
||||
mDecoderStateListener = null;
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
if(mInternalSourceEventListener == null)
|
||||
{
|
||||
mInternalSourceEventListener = new SourceEventListener();
|
||||
}
|
||||
|
||||
return mInternalSourceEventListener;
|
||||
}
|
||||
|
||||
public void setStandardChannelTimeout(long milliseconds)
|
||||
{
|
||||
mStandardChannelFadeTimeout = milliseconds;
|
||||
|
||||
if(mChannel.isStandardChannel())
|
||||
{
|
||||
mFadeTimeout = mStandardChannelFadeTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTrafficChannelTimeout(long milliseconds)
|
||||
{
|
||||
mTrafficChannelFadeTimeout = milliseconds;
|
||||
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected)
|
||||
{
|
||||
mSelected = selected;
|
||||
}
|
||||
|
||||
public boolean isSelected()
|
||||
{
|
||||
return mSelected;
|
||||
}
|
||||
|
||||
public State getState()
|
||||
{
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fade timeout threshold to the current time plus delay
|
||||
*/
|
||||
private void updateFadeTimeout()
|
||||
{
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
|
||||
}
|
||||
else
|
||||
{
|
||||
mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the reset timeout threshold to the current time plus delay
|
||||
*/
|
||||
private void updateResetTimeout()
|
||||
{
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
mEndTimeout = System.currentTimeMillis();
|
||||
}
|
||||
else
|
||||
{
|
||||
mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the squelch state to the registered listener
|
||||
*/
|
||||
protected void broadcast(SquelchState state)
|
||||
{
|
||||
if(mSquelchStateListener != null && !mSquelchLocked)
|
||||
{
|
||||
mSquelchStateListener.receive(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to a registered external source event listener
|
||||
*/
|
||||
protected void broadcast(SourceEvent sourceEvent)
|
||||
{
|
||||
if(mExternalSourceEventListener != null)
|
||||
{
|
||||
mExternalSourceEventListener.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch state listener
|
||||
*/
|
||||
@Override
|
||||
public void setSquelchStateListener(Listener<SquelchState> listener)
|
||||
{
|
||||
mSquelchStateListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the squelch state listener
|
||||
*/
|
||||
@Override
|
||||
public void removeSquelchStateListener()
|
||||
{
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
if(state == mState)
|
||||
{
|
||||
if(State.CALL_STATES.contains(state))
|
||||
{
|
||||
updateFadeTimeout();
|
||||
}
|
||||
}
|
||||
else if(mState.canChangeTo(state))
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case ACTIVE:
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.ACTIVE);
|
||||
break;
|
||||
case CONTROL:
|
||||
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
|
||||
if(mChannel.isStandardChannel())
|
||||
{
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.CONTROL);
|
||||
}
|
||||
break;
|
||||
case DATA:
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.DATA);
|
||||
break;
|
||||
case ENCRYPTED:
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.ENCRYPTED);
|
||||
break;
|
||||
case CALL:
|
||||
broadcast(SquelchState.UNSQUELCH);
|
||||
updateFadeTimeout();
|
||||
mState = state;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.CALL);
|
||||
break;
|
||||
case FADE:
|
||||
processFadeState();
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.FADE);
|
||||
break;
|
||||
case IDLE:
|
||||
processIdleState();
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
|
||||
break;
|
||||
case TEARDOWN:
|
||||
processTeardownState();
|
||||
break;
|
||||
case RESET:
|
||||
mState = State.IDLE;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this channel's sample buffer is in overflow state, meaning that the inbound sample
|
||||
* stream is not being processed fast enough and samples are being thrown away until the processing can
|
||||
* catch up.
|
||||
*
|
||||
* @return true if the channel is in overflow state.
|
||||
*/
|
||||
public boolean isOverflow()
|
||||
{
|
||||
return mSourceOverflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state and processes related actions
|
||||
*/
|
||||
private void processFadeState()
|
||||
{
|
||||
updateResetTimeout();
|
||||
mState = State.FADE;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.FADE);
|
||||
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
}
|
||||
|
||||
private void processIdleState()
|
||||
{
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
|
||||
if(mState == State.FADE)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
|
||||
}
|
||||
|
||||
mState = State.IDLE;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.IDLE);
|
||||
}
|
||||
|
||||
private void processTeardownState()
|
||||
{
|
||||
broadcast(SquelchState.SQUELCH);
|
||||
|
||||
mState = State.TEARDOWN;
|
||||
mIdentifierCollection.update(ChannelStateIdentifier.TEARDOWN);
|
||||
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the call event to the registered listener
|
||||
*/
|
||||
protected void broadcast(IDecodeEvent event)
|
||||
{
|
||||
if(mDecodeEventListener != null)
|
||||
{
|
||||
mDecodeEventListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the channel event to a registered listener
|
||||
*/
|
||||
private void broadcast(ChannelEvent channelEvent)
|
||||
{
|
||||
if(mChannelEventListener != null)
|
||||
{
|
||||
mChannelEventListener.receive(channelEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChannelEventListener(Listener<ChannelEvent> listener)
|
||||
{
|
||||
mChannelEventListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChannelEventListener()
|
||||
{
|
||||
mChannelEventListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a channel state event to any registered listeners
|
||||
*/
|
||||
protected void broadcast(DecoderStateEvent event)
|
||||
{
|
||||
if(mDecoderStateListener != null)
|
||||
{
|
||||
mDecoderStateListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a decoder state event listener
|
||||
*/
|
||||
@Override
|
||||
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
|
||||
{
|
||||
mDecoderStateListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the decoder state event listener
|
||||
*/
|
||||
@Override
|
||||
public void removeDecoderStateListener()
|
||||
{
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<DecoderStateEvent> getDecoderStateListener()
|
||||
{
|
||||
return mDecoderStateEventReceiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive source events from the channel state
|
||||
*/
|
||||
@Override
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mExternalSourceEventListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* De-Registers a listener from receiving source events from the channel state
|
||||
*/
|
||||
@Override
|
||||
public void removeSourceEventListener()
|
||||
{
|
||||
mExternalSourceEventListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to receive source events.
|
||||
*/
|
||||
public class SourceEventListener implements Listener<SourceEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Rebroadcast source frequency change events for the decoder(s) to process
|
||||
long frequency = sourceEvent.getValue().longValue();
|
||||
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency));
|
||||
|
||||
//Create a new frequency configuration identifier so that downstream consumers receive the change
|
||||
//via channel metadata and audio packet updates - this is a silent add that is sent as a notification
|
||||
//to all identifier collections so that they don't rebroadcast the change and cause a feedback loop
|
||||
mIdentifierUpdateNotificationProxy.receive(new IdentifierUpdateNotification(
|
||||
FrequencyConfigurationIdentifier.create(frequency), IdentifierUpdateNotification.Operation.SILENT_ADD));
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||
//Rebroadcast frequency error measurements to external listener if we're currently
|
||||
//in an active (ie sync locked) state.
|
||||
if(getState().isActiveState())
|
||||
{
|
||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
||||
mChannel.getChannelType().name()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DecoderStateEvent receiver wrapper
|
||||
*/
|
||||
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(DecoderStateEvent event)
|
||||
{
|
||||
if(event.getSource() != this)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case ALWAYS_UNSQUELCH:
|
||||
broadcast(SquelchState.UNSQUELCH);
|
||||
mSquelchLocked = true;
|
||||
break;
|
||||
case CHANGE_CALL_TIMEOUT:
|
||||
if(event instanceof ChangeChannelTimeoutEvent)
|
||||
{
|
||||
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
|
||||
|
||||
if(timeout.getChannelType() == ChannelType.STANDARD)
|
||||
{
|
||||
setStandardChannelTimeout(timeout.getCallTimeout());
|
||||
}
|
||||
else
|
||||
{
|
||||
setTrafficChannelTimeout(timeout.getCallTimeout());
|
||||
}
|
||||
}
|
||||
case CONTINUATION:
|
||||
case DECODE:
|
||||
case START:
|
||||
if(State.CALL_STATES.contains(event.getState()))
|
||||
{
|
||||
setState(event.getState());
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
setState(State.TEARDOWN);
|
||||
}
|
||||
else
|
||||
{
|
||||
setState(State.FADE);
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
/* Channel State does not respond to reset events */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
|
||||
* functions.
|
||||
*
|
||||
* 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 HeartbeatReceiver implements Listener<Heartbeat>
|
||||
{
|
||||
@Override
|
||||
public void receive(Heartbeat heartbeat)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
|
||||
{
|
||||
processFadeState();
|
||||
}
|
||||
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
|
||||
{
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
processTeardownState();
|
||||
}
|
||||
else
|
||||
{
|
||||
processIdleState();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable e)
|
||||
{
|
||||
mLog.error("An error occurred while state monitor was running " +
|
||||
"- state [" + getState() +
|
||||
"] current [" + System.currentTimeMillis() +
|
||||
"] mResetTimeout [" + mEndTimeout +
|
||||
"] mFadeTimeout [" + mFadeTimeout +
|
||||
"]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy between the identifier collection and the external update notification listener. This proxy enables
|
||||
* access to internal components to broadcast silent identifier update notifications externally.
|
||||
*/
|
||||
public class IdentifierUpdateNotificationProxy implements Listener<IdentifierUpdateNotification>
|
||||
{
|
||||
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
|
||||
|
||||
@Override
|
||||
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
||||
{
|
||||
if(mIdentifierUpdateNotificationListener != null)
|
||||
{
|
||||
mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateNotificationListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener()
|
||||
{
|
||||
mIdentifierUpdateNotificationListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
|
||||
public interface ChannelStateSelectionListener
|
||||
{
|
||||
public void selected( ChannelState channelState );
|
||||
}
|
||||
@@ -1,43 +1,33 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateProvider;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.message.IMessageListener;
|
||||
import io.github.dsheirer.module.Module;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.event.ActivitySummaryProvider;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEventProvider;
|
||||
import io.github.dsheirer.sample.Broadcaster;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
@@ -46,27 +36,22 @@ import io.github.dsheirer.sample.Listener;
|
||||
*
|
||||
* Provides access to a textual activity summary of events observed.
|
||||
*/
|
||||
public abstract class DecoderState extends Module implements ActivitySummaryProvider, Listener<IMessage>,
|
||||
IDecodeEventProvider, IDecoderStateEventListener, IDecoderStateEventProvider, IMessageListener,
|
||||
IdentifierUpdateProvider, IdentifierUpdateListener
|
||||
public abstract class DecoderState extends AbstractDecoderState
|
||||
{
|
||||
// private final static Logger mLog = LoggerFactory.getLogger(DecoderState.class);
|
||||
private MutableIdentifierCollection mIdentifierCollection;
|
||||
protected Listener<IdentifierUpdateNotification> mConfigurationIdentifierListener;
|
||||
protected IChannelDescriptor mCurrentChannel;
|
||||
|
||||
protected String DIVIDER1 = "======================================================\n";
|
||||
protected String DIVIDER2 = "------------------------------------------------------\n";
|
||||
|
||||
/* This has to be a broadcaster in order for references to persist */
|
||||
private Broadcaster<IDecodeEvent> mDecodeEventBroadcaster = new Broadcaster<>();
|
||||
private Listener<DecoderStateEvent> mDecoderStateListener;
|
||||
private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener();
|
||||
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||
private ConfigurationIdentifierListener mConfigurationIdentifierListener = new ConfigurationIdentifierListener();
|
||||
|
||||
private IChannelDescriptor mCurrentChannel;
|
||||
public DecoderState(MutableIdentifierCollection mutableIdentifierCollection)
|
||||
{
|
||||
mIdentifierCollection = mutableIdentifierCollection;
|
||||
mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType()));
|
||||
}
|
||||
|
||||
public DecoderState()
|
||||
{
|
||||
mIdentifierCollection.update(new DecoderTypeConfigurationIdentifier(getDecoderType()));
|
||||
this(new MutableIdentifierCollection());
|
||||
mConfigurationIdentifierListener = new ConfigurationIdentifierListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,16 +61,6 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
|
||||
mIdentifierCollection.broadcastIdentifiers();
|
||||
}
|
||||
|
||||
public abstract DecoderType getDecoderType();
|
||||
|
||||
/**
|
||||
* Current collection of identifiers managed by the decoder state.
|
||||
*/
|
||||
public MutableIdentifierCollection getIdentifierCollection()
|
||||
{
|
||||
return mIdentifierCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive identifier update notifications
|
||||
*/
|
||||
@@ -105,148 +80,11 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides subclass reference to the decode event broadcaster
|
||||
* Current collection of identifiers managed by the decoder state.
|
||||
*/
|
||||
protected Broadcaster<IDecodeEvent> getDecodeEventBroadcaster()
|
||||
public MutableIdentifierCollection getIdentifierCollection()
|
||||
{
|
||||
return mDecodeEventBroadcaster;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Listener<IMessage> getMessageListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the current temporal state variables to end the current event and prepare for the next.
|
||||
*/
|
||||
protected void resetState()
|
||||
{
|
||||
mIdentifierCollection.remove(IdentifierClass.USER);
|
||||
mCurrentChannel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the decoder state to prepare for processing a different sample
|
||||
* source
|
||||
*/
|
||||
public abstract void reset();
|
||||
|
||||
/**
|
||||
* Allow the decoder to perform any setup actions
|
||||
*/
|
||||
public abstract void init();
|
||||
|
||||
/**
|
||||
* Implements the IDecoderStateEventListener interface to receive state
|
||||
* reset events.
|
||||
*/
|
||||
public abstract void receiveDecoderStateEvent(DecoderStateEvent event);
|
||||
|
||||
/**
|
||||
* Disposes any resources or pointers held by this instance to prepare for
|
||||
* garbage collection
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
mDecodeEventBroadcaster.dispose();
|
||||
mDecodeEventBroadcaster = null;
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity Summary - textual summary of activity observed by the channel state.
|
||||
*/
|
||||
public abstract String getActivitySummary();
|
||||
|
||||
/**
|
||||
* Broadcasts a decode event to any registered listeners
|
||||
*/
|
||||
protected void broadcast(IDecodeEvent event)
|
||||
{
|
||||
mDecodeEventBroadcaster.broadcast(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a call event listener
|
||||
*/
|
||||
@Override
|
||||
public void addDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the call event listener
|
||||
*/
|
||||
@Override
|
||||
public void removeDecodeEventListener(Listener<IDecodeEvent> listener)
|
||||
{
|
||||
mDecodeEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<DecoderStateEvent> getDecoderStateListener()
|
||||
{
|
||||
return mDecoderStateEventListener;
|
||||
}
|
||||
|
||||
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(DecoderStateEvent event)
|
||||
{
|
||||
receiveDecoderStateEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a channel state event to any registered listeners
|
||||
*/
|
||||
protected void broadcast(DecoderStateEvent event)
|
||||
{
|
||||
if(mDecoderStateListener != null)
|
||||
{
|
||||
mDecoderStateListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a decoder state event listener
|
||||
*/
|
||||
@Override
|
||||
public void setDecoderStateListener(Listener<DecoderStateEvent> listener)
|
||||
{
|
||||
mDecoderStateListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the decoder state event listener
|
||||
*/
|
||||
@Override
|
||||
public void removeDecoderStateListener()
|
||||
{
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for configuration identifiers and adds them to this decoder state's identifier collection.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
|
||||
{
|
||||
return mConfigurationIdentifierListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration identifier listener.
|
||||
*/
|
||||
public ConfigurationIdentifierListener getConfigurationIdentifierListener()
|
||||
{
|
||||
return mConfigurationIdentifierListener;
|
||||
return mIdentifierCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,6 +103,54 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
|
||||
mCurrentChannel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the current temporal state variables to end the current event and prepare for the next.
|
||||
*/
|
||||
protected void resetState()
|
||||
{
|
||||
mIdentifierCollection.remove(IdentifierClass.USER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the decoder state to prepare for processing a different sample
|
||||
* source
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
setCurrentChannel(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the decoder to perform any setup actions
|
||||
*/
|
||||
public abstract void init();
|
||||
|
||||
/**
|
||||
* Disposes any resources or pointers held by this instance to prepare for
|
||||
* garbage collection
|
||||
*/
|
||||
public void dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for configuration identifiers and adds them to this decoder state's identifier collection.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
|
||||
{
|
||||
return mConfigurationIdentifierListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration identifier listener.
|
||||
*/
|
||||
public Listener<IdentifierUpdateNotification> getConfigurationIdentifierListener()
|
||||
{
|
||||
return mConfigurationIdentifierListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for configuration type identifier updates sent from the channel state. Adds configuration
|
||||
* identifiers to this decoder state so that decode events will contain configuration details in the
|
||||
@@ -275,28 +161,11 @@ public abstract class DecoderState extends Module implements ActivitySummaryProv
|
||||
@Override
|
||||
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
||||
{
|
||||
if(identifierUpdateNotification.getIdentifier().getIdentifierClass() == IdentifierClass.CONFIGURATION &&
|
||||
identifierUpdateNotification.getIdentifier().getForm() != Form.DECODER_TYPE &&
|
||||
identifierUpdateNotification.getIdentifier().getForm() != Form.CHANNEL_DESCRIPTOR)
|
||||
{
|
||||
if(identifierUpdateNotification.isAdd())
|
||||
{
|
||||
getIdentifierCollection().update(identifierUpdateNotification.getIdentifier());
|
||||
}
|
||||
else if(identifierUpdateNotification.isSilentAdd())
|
||||
{
|
||||
getIdentifierCollection().silentUpdate(identifierUpdateNotification.getIdentifier());
|
||||
}
|
||||
}
|
||||
getIdentifierCollection().receive(identifierUpdateNotification);
|
||||
|
||||
if(identifierUpdateNotification.getOperation() == IdentifierUpdateNotification.Operation.ADD)
|
||||
if(identifierUpdateNotification.getIdentifier() instanceof ChannelDescriptorConfigurationIdentifier)
|
||||
{
|
||||
Identifier identifier = identifierUpdateNotification.getIdentifier();
|
||||
|
||||
if(identifier instanceof ChannelDescriptorConfigurationIdentifier)
|
||||
{
|
||||
mCurrentChannel = ((ChannelDescriptorConfigurationIdentifier)identifier).getValue();
|
||||
}
|
||||
setCurrentChannel(((ChannelDescriptorConfigurationIdentifier)identifierUpdateNotification.getIdentifier()).getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,67 +1,106 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
public class DecoderStateEvent
|
||||
{
|
||||
private Object mSource;
|
||||
private Event mEvent;
|
||||
private State mState;
|
||||
private long mFrequency;
|
||||
|
||||
public DecoderStateEvent( Object source, Event event, State state )
|
||||
{
|
||||
mSource = source;
|
||||
mEvent = event;
|
||||
mState = state;
|
||||
}
|
||||
|
||||
public DecoderStateEvent( Object source, Event event, State state, long frequency )
|
||||
{
|
||||
this( source, event, state );
|
||||
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append( "Decoder State Event - source[" + mSource.getClass() +
|
||||
"] event[" + mEvent.toString() +
|
||||
"] state[" + mState.toString() +
|
||||
"] frequency [" + mFrequency + "]" );
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Object getSource()
|
||||
{
|
||||
return mSource;
|
||||
}
|
||||
|
||||
public Event getEvent()
|
||||
{
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
public State getState()
|
||||
{
|
||||
return mState;
|
||||
}
|
||||
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
public enum Event
|
||||
{
|
||||
ALWAYS_UNSQUELCH,
|
||||
CHANGE_CALL_TIMEOUT,
|
||||
CONTINUATION,
|
||||
DECODE,
|
||||
END,
|
||||
RESET,
|
||||
SOURCE_FREQUENCY,
|
||||
START;
|
||||
}
|
||||
private Object mSource;
|
||||
private Event mEvent;
|
||||
private State mState;
|
||||
private int mTimeslot;
|
||||
private long mFrequency;
|
||||
|
||||
public DecoderStateEvent(Object source, Event event, State state, int timeslot, long frequency)
|
||||
{
|
||||
mSource = source;
|
||||
mEvent = event;
|
||||
mState = state;
|
||||
mTimeslot = timeslot;
|
||||
mFrequency = frequency;
|
||||
}
|
||||
|
||||
public DecoderStateEvent(Object source, Event event, State state, int timeslot)
|
||||
{
|
||||
this(source, event, state, timeslot, 0l);
|
||||
}
|
||||
|
||||
public DecoderStateEvent(Object source, Event event, State state)
|
||||
{
|
||||
this(source, event, state, 0, 0l);
|
||||
}
|
||||
|
||||
public DecoderStateEvent(Object source, Event event, State state, long frequency)
|
||||
{
|
||||
this(source, event, state, 0, frequency);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("Decoder State Event - source[" + mSource.getClass() +
|
||||
"] event[" + mEvent.toString() +
|
||||
"] state[" + mState.toString() +
|
||||
"] timeslot[" + mTimeslot +
|
||||
"] frequency [" + mFrequency + "]");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Object getSource()
|
||||
{
|
||||
return mSource;
|
||||
}
|
||||
|
||||
public Event getEvent()
|
||||
{
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
public State getState()
|
||||
{
|
||||
return mState;
|
||||
}
|
||||
|
||||
public long getFrequency()
|
||||
{
|
||||
return mFrequency;
|
||||
}
|
||||
|
||||
public int getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
}
|
||||
|
||||
public enum Event
|
||||
{
|
||||
ALWAYS_UNSQUELCH,
|
||||
CHANGE_CALL_TIMEOUT,
|
||||
CONTINUATION,
|
||||
DECODE,
|
||||
END,
|
||||
RESET,
|
||||
SOURCE_FREQUENCY,
|
||||
START;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
public interface IStateMachineListener
|
||||
{
|
||||
/**
|
||||
* Indicates that the state has changed for the specified timeslot
|
||||
* @param state of the state machine currently
|
||||
* @param timeslot that the state applies to
|
||||
*/
|
||||
void stateChanged(State state, int timeslot);
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
import io.github.dsheirer.controller.channel.ChannelEvent;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceType;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class MultiChannelState extends AbstractChannelState implements IDecoderStateEventListener, ISourceEventListener,
|
||||
IdentifierUpdateListener, IStateMachineListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MultiChannelState.class);
|
||||
|
||||
public static final long FADE_TIMEOUT_DELAY = 1200;
|
||||
public static final long RESET_TIMEOUT_DELAY = 2000;
|
||||
|
||||
private IdentifierUpdateNotificationProxy mIdentifierUpdateNotificationProxy = new IdentifierUpdateNotificationProxy();
|
||||
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
|
||||
private SourceEventListener mInternalSourceEventListener;
|
||||
private Map<Integer,ChannelMetadata> mChannelMetadataMap = new TreeMap<>();
|
||||
private Map<Integer,MutableIdentifierCollection> mIdentifierCollectionMap = new TreeMap<>();
|
||||
private Map<Integer,StateMachine> mStateMachineMap = new TreeMap<>();
|
||||
private Map<Integer,StateMonitoringSquelchController> mSquelchControllerMap = new TreeMap<>();
|
||||
private int mTimeslotCount;
|
||||
private Listener<IdentifierUpdateNotification> mIdentifierUpdateListener = new IdentifierUpdateListenerProxy();
|
||||
|
||||
/**
|
||||
* Multi-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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
public MultiChannelState(Channel channel, AliasModel aliasModel, int timeslots)
|
||||
{
|
||||
super(channel);
|
||||
|
||||
mTimeslotCount = timeslots;
|
||||
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
{
|
||||
mChannelMetadataMap.put(timeslot, new ChannelMetadata(aliasModel, timeslot));
|
||||
MutableIdentifierCollection mutableIdentifierCollection = new MutableIdentifierCollection(timeslot);
|
||||
mIdentifierCollectionMap.put(timeslot, mutableIdentifierCollection);
|
||||
|
||||
//Set the proxy as a listener so that echo'd updates are broadcast externally
|
||||
mutableIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
|
||||
|
||||
StateMachine stateMachine = new StateMachine(timeslot);
|
||||
mStateMachineMap.put(timeslot, stateMachine);
|
||||
stateMachine.addListener(this);
|
||||
|
||||
StateMonitoringSquelchController squelchController = new StateMonitoringSquelchController(timeslot);
|
||||
mSquelchControllerMap.put(timeslot, squelchController);
|
||||
stateMachine.addListener(squelchController);
|
||||
|
||||
stateMachine.setChannelType(mChannel.getChannelType());
|
||||
stateMachine.setIdentifierUpdateListener(mutableIdentifierCollection);
|
||||
stateMachine.setEndTimeoutBuffer(RESET_TIMEOUT_DELAY);
|
||||
if(channel.getChannelType() == ChannelType.STANDARD)
|
||||
{
|
||||
stateMachine.setFadeTimeoutBuffer(FADE_TIMEOUT_DELAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
stateMachine.setFadeTimeoutBuffer(DecodeConfiguration.DEFAULT_CALL_TIMEOUT_DELAY_SECONDS * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
createConfigurationIdentifiers(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(State state, int timeslot)
|
||||
{
|
||||
ChannelStateIdentifier stateIdentifier = ChannelStateIdentifier.create(state);
|
||||
mIdentifierCollectionMap.get(timeslot).update(stateIdentifier);
|
||||
mChannelMetadataMap.get(timeslot).receive(new IdentifierUpdateNotification(stateIdentifier, IdentifierUpdateNotification.Operation.ADD, timeslot));
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case IDLE:
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
|
||||
break;
|
||||
case RESET:
|
||||
reset(timeslot);
|
||||
mStateMachineMap.get(timeslot).setState(State.IDLE);
|
||||
break;
|
||||
case TEARDOWN:
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
checkTeardown();
|
||||
}
|
||||
else
|
||||
{
|
||||
mStateMachineMap.get(timeslot).setState(State.RESET);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the state of each timeslot and issues a teardown request if all timeslots are inactive
|
||||
*/
|
||||
private void checkTeardown()
|
||||
{
|
||||
boolean teardown = true;
|
||||
|
||||
for(StateMachine stateMachine: mStateMachineMap.values())
|
||||
{
|
||||
if(stateMachine.getState().isActiveState())
|
||||
{
|
||||
teardown = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(teardown)
|
||||
{
|
||||
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkState()
|
||||
{
|
||||
for(StateMachine stateMachine: mStateMachineMap.values())
|
||||
{
|
||||
stateMachine.checkState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates configuration identifiers for the channel name, system, site and alias list name.
|
||||
*/
|
||||
private void createConfigurationIdentifiers(Channel channel)
|
||||
{
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
{
|
||||
MutableIdentifierCollection identifierCollection = mIdentifierCollectionMap.get(timeslot);
|
||||
|
||||
identifierCollection.update(DecoderTypeConfigurationIdentifier.create(channel.getDecodeConfiguration().getDecoderType()));
|
||||
|
||||
if(channel.hasSystem())
|
||||
{
|
||||
identifierCollection.update(SystemConfigurationIdentifier.create(channel.getSystem()));
|
||||
}
|
||||
if(channel.hasSite())
|
||||
{
|
||||
identifierCollection.update(SiteConfigurationIdentifier.create(channel.getSite()));
|
||||
}
|
||||
if(channel.getName() != null && !channel.getName().isEmpty())
|
||||
{
|
||||
identifierCollection.update(ChannelNameConfigurationIdentifier.create(channel.getName()));
|
||||
}
|
||||
if(channel.getAliasListName() != null && !channel.getAliasListName().isEmpty())
|
||||
{
|
||||
identifierCollection.update(AliasListConfigurationIdentifier.create(channel.getAliasListName()));
|
||||
}
|
||||
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
||||
{
|
||||
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
|
||||
identifierCollection.update(FrequencyConfigurationIdentifier.create(frequency));
|
||||
}
|
||||
else if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
||||
{
|
||||
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencies();
|
||||
|
||||
if(frequencies.size() > 0)
|
||||
{
|
||||
identifierCollection.update(FrequencyConfigurationIdentifier.create(frequencies.get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to receive channel identifier updates from this channel state and from any
|
||||
* decoder states.
|
||||
*/
|
||||
@Override
|
||||
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
|
||||
{
|
||||
return mIdentifierUpdateListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the external listener that will receive identifier update notifications produced by this channel state
|
||||
* @param listener
|
||||
*/
|
||||
@Override
|
||||
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateNotificationProxy.setListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the external listener from receiving identifier update notifications produced by this channel state
|
||||
*/
|
||||
@Override
|
||||
public void removeIdentifierUpdateListener()
|
||||
{
|
||||
mIdentifierUpdateNotificationProxy.setListener(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel state identifier collection using the update notification. This update will be reflected
|
||||
* in the internal channel state and will also be broadcast to any listeners, including the channel metadata for
|
||||
* this channel state.
|
||||
*/
|
||||
@Override
|
||||
public void updateChannelStateIdentifiers(IdentifierUpdateNotification notification)
|
||||
{
|
||||
//Explicitly add or remove the identifier from the local identifier collection to allow it to be rebroadcast
|
||||
//to external listeners, which includes this state's channel metadata
|
||||
MutableIdentifierCollection identifierCollection = mIdentifierCollectionMap.get(notification.getTimeslot());
|
||||
|
||||
if(identifierCollection != null)
|
||||
{
|
||||
if(notification.isAdd())
|
||||
{
|
||||
identifierCollection.update(notification.getIdentifier());
|
||||
}
|
||||
else if(notification.isSilentAdd())
|
||||
{
|
||||
identifierCollection.silentUpdate(notification.getIdentifier());
|
||||
}
|
||||
else if(notification.isRemove())
|
||||
{
|
||||
identifierCollection.remove(notification.getIdentifier());
|
||||
}
|
||||
else if(notification.isSilentRemove())
|
||||
{
|
||||
identifierCollection.silentRemove(notification.getIdentifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel metadata for this channel.
|
||||
*/
|
||||
public Collection<ChannelMetadata> getChannelMetadata()
|
||||
{
|
||||
return mChannelMetadataMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this channel state and prepares it for reuse.
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
{
|
||||
reset(timeslot);
|
||||
}
|
||||
|
||||
sourceOverflow(false);
|
||||
}
|
||||
|
||||
private void reset(int timeslot)
|
||||
{
|
||||
mStateMachineMap.get(timeslot).setState(State.RESET);
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE, timeslot));
|
||||
MutableIdentifierCollection identifierCollection = mIdentifierCollectionMap.get(timeslot);
|
||||
identifierCollection.remove(IdentifierClass.USER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
{
|
||||
mIdentifierCollectionMap.get(timeslot).broadcastIdentifiers();
|
||||
|
||||
if(mChannel.getChannelType() == ChannelType.TRAFFIC)
|
||||
{
|
||||
mStateMachineMap.get(timeslot).setState(State.ACTIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
for(StateMonitoringSquelchController squelchController: mSquelchControllerMap.values())
|
||||
{
|
||||
squelchController.setSquelchLock(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mDecodeEventListener = null;
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
if(mInternalSourceEventListener == null)
|
||||
{
|
||||
mInternalSourceEventListener = new SourceEventListener();
|
||||
}
|
||||
|
||||
return mInternalSourceEventListener;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setSquelchStateListener(Listener<SquelchStateEvent> listener)
|
||||
{
|
||||
for(StateMonitoringSquelchController squelchController: mSquelchControllerMap.values())
|
||||
{
|
||||
squelchController.setSquelchStateListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSquelchStateListener()
|
||||
{
|
||||
for(StateMonitoringSquelchController squelchController: mSquelchControllerMap.values())
|
||||
{
|
||||
squelchController.removeSquelchStateListener();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to a registered external source event listener
|
||||
*/
|
||||
protected void broadcast(SourceEvent sourceEvent)
|
||||
{
|
||||
if(mExternalSourceEventListener != null)
|
||||
{
|
||||
mExternalSourceEventListener.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the call event to the registered listener
|
||||
*/
|
||||
protected void broadcast(IDecodeEvent event)
|
||||
{
|
||||
if(mDecodeEventListener != null)
|
||||
{
|
||||
mDecodeEventListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the channel event to a registered listener
|
||||
*/
|
||||
private void broadcast(ChannelEvent channelEvent)
|
||||
{
|
||||
if(mChannelEventListener != null)
|
||||
{
|
||||
mChannelEventListener.receive(channelEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a channel state event to any registered listeners
|
||||
*/
|
||||
protected void broadcast(DecoderStateEvent event)
|
||||
{
|
||||
if(mDecoderStateListener != null)
|
||||
{
|
||||
mDecoderStateListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<DecoderStateEvent> getDecoderStateListener()
|
||||
{
|
||||
return mDecoderStateEventReceiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to receive source events.
|
||||
*/
|
||||
public class SourceEventListener implements Listener<SourceEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Rebroadcast source frequency change events for the decoder(s) to process
|
||||
long frequency = sourceEvent.getValue().longValue();
|
||||
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
{
|
||||
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY,
|
||||
mStateMachineMap.get(timeslot).getState(), frequency));
|
||||
|
||||
//Create a new frequency configuration identifier so that downstream consumers receive the change
|
||||
//via channel metadata and audio packet updates - this is a silent add that is sent as a notification
|
||||
//to all identifier collections so that they don't rebroadcast the change and cause a feedback loop
|
||||
|
||||
mIdentifierUpdateNotificationProxy.receive(new IdentifierUpdateNotification(
|
||||
FrequencyConfigurationIdentifier.create(frequency), IdentifierUpdateNotification.Operation.SILENT_ADD, timeslot));
|
||||
}
|
||||
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||
//Rebroadcast frequency error measurements to external listener if we're currently
|
||||
//in an active (ie sync locked) state.
|
||||
for(int timeslot = 0; timeslot < mTimeslotCount; timeslot++)
|
||||
{
|
||||
if(mStateMachineMap.get(timeslot).getState().isActiveState())
|
||||
{
|
||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
||||
mChannel.getChannelType().name()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DecoderStateEvent receiver wrapper
|
||||
*/
|
||||
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(DecoderStateEvent event)
|
||||
{
|
||||
if(event.getSource() != this)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case ALWAYS_UNSQUELCH:
|
||||
mSquelchControllerMap.get(event.getTimeslot()).setSquelchLock(true);
|
||||
break;
|
||||
case CHANGE_CALL_TIMEOUT:
|
||||
if(event instanceof ChangeChannelTimeoutEvent)
|
||||
{
|
||||
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
|
||||
mStateMachineMap.get(event.getTimeslot()).setFadeTimeoutBuffer(timeout.getCallTimeout());
|
||||
}
|
||||
case CONTINUATION:
|
||||
case DECODE:
|
||||
case START:
|
||||
if(State.CALL_STATES.contains(event.getState()))
|
||||
{
|
||||
mStateMachineMap.get(event.getTimeslot()).setState(event.getState());
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
mStateMachineMap.get(event.getTimeslot()).setState(State.TEARDOWN);
|
||||
}
|
||||
else
|
||||
{
|
||||
mStateMachineMap.get(event.getTimeslot()).setState(State.FADE);
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
/* Channel State does not respond to reset events */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives and passes identifier update notifications to the correct channel metadata collections
|
||||
*/
|
||||
public class IdentifierUpdateListenerProxy implements Listener<IdentifierUpdateNotification>
|
||||
{
|
||||
@Override
|
||||
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
||||
{
|
||||
int timeslot = identifierUpdateNotification.getTimeslot();
|
||||
|
||||
ChannelMetadata channelMetadata = mChannelMetadataMap.get(timeslot);
|
||||
|
||||
if(channelMetadata != null)
|
||||
{
|
||||
channelMetadata.receive(identifierUpdateNotification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy between the internal identifier collections and the external update notification listener. This proxy
|
||||
* enables access to internal components to broadcast silent identifier update notifications externally.
|
||||
*/
|
||||
public class IdentifierUpdateNotificationProxy implements Listener<IdentifierUpdateNotification>
|
||||
{
|
||||
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
|
||||
|
||||
@Override
|
||||
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
||||
{
|
||||
if(mIdentifierUpdateNotificationListener != null)
|
||||
{
|
||||
mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateNotificationListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener()
|
||||
{
|
||||
mIdentifierUpdateNotificationListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.channel.metadata.ChannelMetadata;
|
||||
import io.github.dsheirer.channel.state.DecoderStateEvent.Event;
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||
import io.github.dsheirer.controller.channel.ChannelEvent;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateListener;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.ChannelNameConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.DecoderTypeConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.FrequencyConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier;
|
||||
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.ISourceEventListener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.SourceType;
|
||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Identifiers and Channel Metadata
|
||||
*
|
||||
* The internal identifier collection maintains all of the channel configuration items and monitors source events to
|
||||
* update the channel's frequency as it changes. These updates are broadcast externally to any identifier collection
|
||||
* listeners. The internal identifier collection does not listen to incoming identifier update notifications in order
|
||||
* to prevent a feedback loop. Channel Metadata is the only listener for externally generated updates.
|
||||
*
|
||||
* Channel Metadata receives all external identifier notifications and any internal notifications generated by the
|
||||
* internal identifier collection via feedback.
|
||||
*/
|
||||
public class SingleChannelState extends AbstractChannelState implements IDecoderStateEventListener, ISourceEventListener,
|
||||
IdentifierUpdateListener, IStateMachineListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(SingleChannelState.class);
|
||||
|
||||
public static final long FADE_TIMEOUT_DELAY = 1200;
|
||||
public static final long RESET_TIMEOUT_DELAY = 2000;
|
||||
|
||||
private MutableIdentifierCollection mIdentifierCollection = new MutableIdentifierCollection();
|
||||
private IdentifierUpdateNotificationProxy mIdentifierUpdateNotificationProxy = new IdentifierUpdateNotificationProxy();
|
||||
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
|
||||
private SourceEventListener mInternalSourceEventListener;
|
||||
private ChannelMetadata mChannelMetadata;
|
||||
private StateMachine mStateMachine = new StateMachine(0);
|
||||
private StateMonitoringSquelchController mSquelchController = new StateMonitoringSquelchController(0);
|
||||
|
||||
public SingleChannelState(Channel channel, AliasModel aliasModel)
|
||||
{
|
||||
super(channel);
|
||||
mChannelMetadata = new ChannelMetadata(aliasModel);
|
||||
mIdentifierCollection.setIdentifierUpdateListener(mIdentifierUpdateNotificationProxy);
|
||||
createConfigurationIdentifiers(channel);
|
||||
|
||||
mStateMachine.addListener(this);
|
||||
mStateMachine.addListener(mSquelchController);
|
||||
mStateMachine.setChannelType(mChannel.getChannelType());
|
||||
mStateMachine.setIdentifierUpdateListener(mIdentifierCollection);
|
||||
mStateMachine.setEndTimeoutBuffer(RESET_TIMEOUT_DELAY);
|
||||
if(channel.getChannelType() == ChannelType.STANDARD)
|
||||
{
|
||||
mStateMachine.setFadeTimeoutBuffer(FADE_TIMEOUT_DELAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
mStateMachine.setFadeTimeoutBuffer(DecodeConfiguration.DEFAULT_CALL_TIMEOUT_DELAY_SECONDS * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(State state, int timeslot)
|
||||
{
|
||||
ChannelStateIdentifier stateIdentifier = ChannelStateIdentifier.create(state);
|
||||
mIdentifierCollection.update(stateIdentifier);
|
||||
mChannelMetadata.receive(new IdentifierUpdateNotification(stateIdentifier, IdentifierUpdateNotification.Operation.ADD, timeslot));
|
||||
|
||||
switch(state)
|
||||
{
|
||||
case IDLE:
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
|
||||
break;
|
||||
case RESET:
|
||||
reset();
|
||||
mStateMachine.setState(State.IDLE);
|
||||
break;
|
||||
case TEARDOWN:
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
broadcast(new ChannelEvent(mChannel, ChannelEvent.Event.REQUEST_DISABLE));
|
||||
}
|
||||
else
|
||||
{
|
||||
mStateMachine.setState(State.RESET);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkState()
|
||||
{
|
||||
mStateMachine.checkState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateNotificationProxy.setListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeIdentifierUpdateListener()
|
||||
{
|
||||
mIdentifierUpdateNotificationProxy.removeListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSquelchStateListener(Listener<SquelchStateEvent> listener)
|
||||
{
|
||||
mSquelchController.setSquelchStateListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSquelchStateListener()
|
||||
{
|
||||
mSquelchController.removeSquelchStateListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates configuration identifiers for the channel name, system, site and alias list name.
|
||||
*/
|
||||
private void createConfigurationIdentifiers(Channel channel)
|
||||
{
|
||||
mIdentifierCollection.update(DecoderTypeConfigurationIdentifier.create(channel.getDecodeConfiguration().getDecoderType()));
|
||||
|
||||
if(channel.hasSystem())
|
||||
{
|
||||
mIdentifierCollection.update(SystemConfigurationIdentifier.create(channel.getSystem()));
|
||||
}
|
||||
if(channel.hasSite())
|
||||
{
|
||||
mIdentifierCollection.update(SiteConfigurationIdentifier.create(channel.getSite()));
|
||||
}
|
||||
if(channel.getName() != null && !channel.getName().isEmpty())
|
||||
{
|
||||
mIdentifierCollection.update(ChannelNameConfigurationIdentifier.create(channel.getName()));
|
||||
}
|
||||
if(channel.getAliasListName() != null && !channel.getAliasListName().isEmpty())
|
||||
{
|
||||
mIdentifierCollection.update(AliasListConfigurationIdentifier.create(channel.getAliasListName()));
|
||||
}
|
||||
if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER)
|
||||
{
|
||||
long frequency = ((SourceConfigTuner)channel.getSourceConfiguration()).getFrequency();
|
||||
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequency));
|
||||
}
|
||||
else if(channel.getSourceConfiguration().getSourceType() == SourceType.TUNER_MULTIPLE_FREQUENCIES)
|
||||
{
|
||||
List<Long> frequencies = ((SourceConfigTunerMultipleFrequency)channel.getSourceConfiguration()).getFrequencies();
|
||||
|
||||
if(frequencies.size() > 0)
|
||||
{
|
||||
mIdentifierCollection.update(FrequencyConfigurationIdentifier.create(frequencies.get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to receive channel identifier updates from this channel state and from any
|
||||
* decoder states.
|
||||
*/
|
||||
@Override
|
||||
public Listener<IdentifierUpdateNotification> getIdentifierUpdateListener()
|
||||
{
|
||||
return mChannelMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the channel state identifier collection using the update notification. This update will be reflected
|
||||
* in the internal channel state and will also be broadcast to any listeners, including the channel metadata for
|
||||
* this channel state.
|
||||
*/
|
||||
@Override
|
||||
public void updateChannelStateIdentifiers(IdentifierUpdateNotification notification)
|
||||
{
|
||||
mIdentifierCollection.receive(notification);
|
||||
mChannelMetadata.receive(notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel metadata for this channel.
|
||||
*/
|
||||
public Collection<ChannelMetadata> getChannelMetadata()
|
||||
{
|
||||
return Collections.singletonList(mChannelMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this channel state and prepares it for reuse.
|
||||
*/
|
||||
@Override
|
||||
public void reset()
|
||||
{
|
||||
mStateMachine.setState(State.RESET);
|
||||
broadcast(new DecoderStateEvent(this, Event.RESET, State.IDLE));
|
||||
mIdentifierCollection.remove(IdentifierClass.USER);
|
||||
sourceOverflow(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start()
|
||||
{
|
||||
mIdentifierCollection.broadcastIdentifiers();
|
||||
|
||||
if(mChannel.getChannelType() == ChannelType.TRAFFIC)
|
||||
{
|
||||
mStateMachine.setState(State.ACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop()
|
||||
{
|
||||
mSquelchController.setSquelchLock(false);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mDecodeEventListener = null;
|
||||
mDecoderStateListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<SourceEvent> getSourceEventListener()
|
||||
{
|
||||
if(mInternalSourceEventListener == null)
|
||||
{
|
||||
mInternalSourceEventListener = new SourceEventListener();
|
||||
}
|
||||
|
||||
return mInternalSourceEventListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to a registered external source event listener
|
||||
*/
|
||||
protected void broadcast(SourceEvent sourceEvent)
|
||||
{
|
||||
if(mExternalSourceEventListener != null)
|
||||
{
|
||||
mExternalSourceEventListener.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the call event to the registered listener
|
||||
*/
|
||||
protected void broadcast(IDecodeEvent event)
|
||||
{
|
||||
if(mDecodeEventListener != null)
|
||||
{
|
||||
mDecodeEventListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the channel event to a registered listener
|
||||
*/
|
||||
private void broadcast(ChannelEvent channelEvent)
|
||||
{
|
||||
if(mChannelEventListener != null)
|
||||
{
|
||||
mChannelEventListener.receive(channelEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a channel state event to any registered listeners
|
||||
*/
|
||||
protected void broadcast(DecoderStateEvent event)
|
||||
{
|
||||
if(mDecoderStateListener != null)
|
||||
{
|
||||
mDecoderStateListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listener<DecoderStateEvent> getDecoderStateListener()
|
||||
{
|
||||
return mDecoderStateEventReceiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to receive source events.
|
||||
*/
|
||||
public class SourceEventListener implements Listener<SourceEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case NOTIFICATION_FREQUENCY_CHANGE:
|
||||
//Rebroadcast source frequency change events for the decoder(s) to process
|
||||
long frequency = sourceEvent.getValue().longValue();
|
||||
broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, mStateMachine.getState(), frequency));
|
||||
|
||||
//Create a new frequency configuration identifier so that downstream consumers receive the change
|
||||
//via channel metadata and audio packet updates - this is a silent add that is sent as a notification
|
||||
//to all identifier collections so that they don't rebroadcast the change and cause a feedback loop
|
||||
mIdentifierUpdateNotificationProxy.receive(new IdentifierUpdateNotification(
|
||||
FrequencyConfigurationIdentifier.create(frequency), IdentifierUpdateNotification.Operation.SILENT_ADD, 0));
|
||||
break;
|
||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR:
|
||||
//Rebroadcast frequency error measurements to external listener if we're currently
|
||||
//in an active (ie sync locked) state.
|
||||
if(mStateMachine.getState().isActiveState())
|
||||
{
|
||||
broadcast(SourceEvent.frequencyErrorMeasurementSyncLocked(sourceEvent.getValue().longValue(),
|
||||
mChannel.getChannelType().name()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DecoderStateEvent receiver wrapper
|
||||
*/
|
||||
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
|
||||
{
|
||||
@Override
|
||||
public void receive(DecoderStateEvent event)
|
||||
{
|
||||
if(event.getSource() != this)
|
||||
{
|
||||
switch(event.getEvent())
|
||||
{
|
||||
case ALWAYS_UNSQUELCH:
|
||||
mSquelchController.setSquelchLock(true);
|
||||
break;
|
||||
case CHANGE_CALL_TIMEOUT:
|
||||
if(event instanceof ChangeChannelTimeoutEvent)
|
||||
{
|
||||
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
|
||||
mStateMachine.setFadeTimeoutBuffer(timeout.getCallTimeout());
|
||||
}
|
||||
case CONTINUATION:
|
||||
case DECODE:
|
||||
case START:
|
||||
if(State.CALL_STATES.contains(event.getState()))
|
||||
{
|
||||
mStateMachine.setState(event.getState());
|
||||
}
|
||||
break;
|
||||
case END:
|
||||
if(mChannel.isTrafficChannel())
|
||||
{
|
||||
mStateMachine.setState(State.TEARDOWN);
|
||||
}
|
||||
else
|
||||
{
|
||||
mStateMachine.setState(State.FADE);
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
/* Channel State does not respond to reset events */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: this can probably be removed. The only reason it exists is so that we can inject frequency change
|
||||
//updates directly to the external broadcaster as a silent add. Otherwise, we could simply register the external
|
||||
//listener directly on the identifier collection.
|
||||
|
||||
/**
|
||||
* Proxy between the internal identifier collection and the external update notification listener. This proxy
|
||||
* enables access to internal components to broadcast silent identifier update notifications externally.
|
||||
*/
|
||||
public class IdentifierUpdateNotificationProxy implements Listener<IdentifierUpdateNotification>
|
||||
{
|
||||
private Listener<IdentifierUpdateNotification> mIdentifierUpdateNotificationListener;
|
||||
|
||||
@Override
|
||||
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
||||
{
|
||||
if(mIdentifierUpdateNotificationListener != null)
|
||||
{
|
||||
mIdentifierUpdateNotificationListener.receive(identifierUpdateNotification);
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateNotificationListener = listener;
|
||||
}
|
||||
|
||||
public void removeListener()
|
||||
{
|
||||
mIdentifierUpdateNotificationListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +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.
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
*/
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import java.util.EnumSet;
|
||||
@@ -39,7 +43,8 @@ public enum State
|
||||
state == DATA ||
|
||||
state == ENCRYPTED ||
|
||||
state == FADE ||
|
||||
state == TEARDOWN;
|
||||
state == TEARDOWN ||
|
||||
state == RESET;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -55,7 +60,8 @@ public enum State
|
||||
state == DATA ||
|
||||
state == ENCRYPTED ||
|
||||
state == FADE ||
|
||||
state == TEARDOWN;
|
||||
state == TEARDOWN ||
|
||||
state == RESET;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -67,7 +73,8 @@ public enum State
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == IDLE ||
|
||||
state == FADE;
|
||||
state == FADE ||
|
||||
state == RESET;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -83,6 +90,7 @@ public enum State
|
||||
state == CONTROL ||
|
||||
state == ENCRYPTED ||
|
||||
state == FADE ||
|
||||
state == RESET ||
|
||||
state == TEARDOWN;
|
||||
}
|
||||
},
|
||||
@@ -95,7 +103,8 @@ public enum State
|
||||
public boolean canChangeTo(State state)
|
||||
{
|
||||
return state == FADE ||
|
||||
state == TEARDOWN;
|
||||
state == TEARDOWN ||
|
||||
state == RESET;
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
214
src/main/java/io/github/dsheirer/channel/state/StateMachine.java
Normal file
214
src/main/java/io/github/dsheirer/channel/state/StateMachine.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.controller.channel.Channel;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.decoder.ChannelStateIdentifier;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class StateMachine
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(StateMachine.class);
|
||||
|
||||
protected State mState = State.IDLE;
|
||||
protected long mFadeTimeout;
|
||||
protected long mFadeTimeoutBuffer = 0;
|
||||
protected long mEndTimeout;
|
||||
protected long mEndTimeoutBuffer = 0;
|
||||
private int mTimeslot;
|
||||
private Channel.ChannelType mChannelType = Channel.ChannelType.STANDARD;
|
||||
private List<IStateMachineListener> mStateMachineListeners = new ArrayList<>();
|
||||
private Listener<IdentifierUpdateNotification> mIdentifierUpdateListener;
|
||||
|
||||
public StateMachine(int timeslot)
|
||||
{
|
||||
mTimeslot = timeslot;
|
||||
}
|
||||
|
||||
public void addListener(IStateMachineListener listener)
|
||||
{
|
||||
mStateMachineListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(IStateMachineListener listener)
|
||||
{
|
||||
mStateMachineListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void setIdentifierUpdateListener(Listener<IdentifierUpdateNotification> listener)
|
||||
{
|
||||
mIdentifierUpdateListener = listener;
|
||||
}
|
||||
|
||||
public void setChannelType(Channel.ChannelType channelType)
|
||||
{
|
||||
mChannelType = channelType;
|
||||
}
|
||||
|
||||
public void checkState()
|
||||
{
|
||||
if(mState.isActiveState() && mFadeTimeout <= System.currentTimeMillis())
|
||||
{
|
||||
setState(State.FADE);
|
||||
}
|
||||
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
|
||||
{
|
||||
setState(State.TEARDOWN);
|
||||
}
|
||||
}
|
||||
|
||||
public State getState()
|
||||
{
|
||||
return mState;
|
||||
}
|
||||
|
||||
public void setState(State state)
|
||||
{
|
||||
if(state == mState)
|
||||
{
|
||||
if(State.CALL_STATES.contains(state))
|
||||
{
|
||||
updateFadeTimeout();
|
||||
}
|
||||
}
|
||||
else if(mState.canChangeTo(state))
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case ACTIVE:
|
||||
mState = state;
|
||||
updateFadeTimeout();
|
||||
broadcast(ChannelStateIdentifier.ACTIVE);
|
||||
break;
|
||||
case CONTROL:
|
||||
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
|
||||
if(mChannelType == Channel.ChannelType.STANDARD)
|
||||
{
|
||||
mState = state;
|
||||
updateFadeTimeout();
|
||||
broadcast(ChannelStateIdentifier.CONTROL);
|
||||
}
|
||||
break;
|
||||
case DATA:
|
||||
mState = state;
|
||||
updateFadeTimeout();
|
||||
broadcast(ChannelStateIdentifier.DATA);
|
||||
break;
|
||||
case ENCRYPTED:
|
||||
mState = state;
|
||||
updateFadeTimeout();
|
||||
broadcast(ChannelStateIdentifier.ENCRYPTED);
|
||||
break;
|
||||
case CALL:
|
||||
mState = state;
|
||||
updateFadeTimeout();
|
||||
broadcast(ChannelStateIdentifier.CALL);
|
||||
break;
|
||||
case FADE:
|
||||
mState = state;
|
||||
broadcast(ChannelStateIdentifier.FADE);
|
||||
break;
|
||||
case IDLE:
|
||||
mState = state;
|
||||
broadcast(ChannelStateIdentifier.IDLE);
|
||||
break;
|
||||
case TEARDOWN:
|
||||
mState = state;
|
||||
broadcast(ChannelStateIdentifier.TEARDOWN);
|
||||
break;
|
||||
case RESET:
|
||||
mState = State.RESET;
|
||||
broadcast(ChannelStateIdentifier.RESET);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
//If the state successfully changed to the new state, announce it
|
||||
if(mState == state)
|
||||
{
|
||||
for(IStateMachineListener listener: mStateMachineListeners)
|
||||
{
|
||||
listener.stateChanged(mState, mTimeslot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcast(ChannelStateIdentifier channelStateIdentifier)
|
||||
{
|
||||
if(mIdentifierUpdateListener != null)
|
||||
{
|
||||
mIdentifierUpdateListener.receive(new IdentifierUpdateNotification(channelStateIdentifier,
|
||||
IdentifierUpdateNotification.Operation.ADD, mTimeslot));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFadeTimeout()
|
||||
{
|
||||
mFadeTimeout = System.currentTimeMillis() + mFadeTimeoutBuffer;
|
||||
}
|
||||
|
||||
public void setFadeTimeout(long timeout)
|
||||
{
|
||||
mFadeTimeout = timeout;
|
||||
}
|
||||
|
||||
public long getFadeTimeout()
|
||||
{
|
||||
return mFadeTimeout;
|
||||
}
|
||||
|
||||
public void setFadeTimeoutBuffer(long buffer)
|
||||
{
|
||||
mFadeTimeoutBuffer = buffer;
|
||||
updateFadeTimeout();
|
||||
}
|
||||
|
||||
private void updateEndTimeout()
|
||||
{
|
||||
mEndTimeout = System.currentTimeMillis() + mEndTimeoutBuffer;
|
||||
}
|
||||
|
||||
public void setEndTimeoutBuffer(long buffer)
|
||||
{
|
||||
mEndTimeoutBuffer = buffer;
|
||||
updateEndTimeout();
|
||||
}
|
||||
|
||||
public void setEndTimeout(long endTimeout)
|
||||
{
|
||||
mEndTimeout = endTimeout;
|
||||
}
|
||||
|
||||
public long getEndTimeout()
|
||||
{
|
||||
return mEndTimeout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.audio.squelch.ISquelchStateProvider;
|
||||
import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.audio.squelch.SquelchStateEvent;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
public class StateMonitoringSquelchController implements IStateMachineListener, ISquelchStateProvider
|
||||
{
|
||||
private boolean mSquelchLocked;
|
||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||
private Listener<SquelchStateEvent> mSquelchStateListener;
|
||||
private int mTimeslot;
|
||||
|
||||
public StateMonitoringSquelchController(int timeslot)
|
||||
{
|
||||
mTimeslot = timeslot;
|
||||
}
|
||||
|
||||
public void setSquelchStateListener(Listener<SquelchStateEvent> listener)
|
||||
{
|
||||
mSquelchStateListener = listener;
|
||||
}
|
||||
|
||||
public void removeSquelchStateListener()
|
||||
{
|
||||
mSquelchStateListener = null;
|
||||
}
|
||||
|
||||
public void setSquelchLock(boolean locked)
|
||||
{
|
||||
mSquelchLocked = locked;
|
||||
setSquelchState(mSquelchLocked ? SquelchState.UNSQUELCH : SquelchState.SQUELCH);
|
||||
}
|
||||
|
||||
private void setSquelchState(SquelchState squelchState)
|
||||
{
|
||||
if(mSquelchState != squelchState)
|
||||
{
|
||||
mSquelchState = squelchState;
|
||||
|
||||
if(mSquelchStateListener != null)
|
||||
{
|
||||
mSquelchStateListener.receive(new SquelchStateEvent(squelchState, mTimeslot));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(State state, int timeslot)
|
||||
{
|
||||
if(!mSquelchLocked)
|
||||
{
|
||||
if(state == State.CALL)
|
||||
{
|
||||
setSquelchState(SquelchState.UNSQUELCH);
|
||||
}
|
||||
else
|
||||
{
|
||||
setSquelchState(SquelchState.SQUELCH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.channel.state;
|
||||
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierClass;
|
||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||
import io.github.dsheirer.identifier.configuration.ChannelDescriptorConfigurationIdentifier;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
public abstract class TimeslotDecoderState extends DecoderState
|
||||
{
|
||||
private int mTimeslot;
|
||||
|
||||
public TimeslotDecoderState(int timeslot)
|
||||
{
|
||||
super(new MutableIdentifierCollection(timeslot));
|
||||
mTimeslot = timeslot;
|
||||
mConfigurationIdentifierListener = new TimeslotConfigurationIdentifierListener();
|
||||
}
|
||||
|
||||
protected int getTimeslot()
|
||||
{
|
||||
return mTimeslot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for configuration type identifier updates sent from the channel state. Adds configuration
|
||||
* identifiers to this decoder state so that decode events will contain configuration details in the
|
||||
* event's identifier collection.
|
||||
*/
|
||||
public class TimeslotConfigurationIdentifierListener implements Listener<IdentifierUpdateNotification>
|
||||
{
|
||||
@Override
|
||||
public void receive(IdentifierUpdateNotification identifierUpdateNotification)
|
||||
{
|
||||
if(identifierUpdateNotification.getTimeslot() == getTimeslot())
|
||||
{
|
||||
if(identifierUpdateNotification.getIdentifier().getIdentifierClass() == IdentifierClass.CONFIGURATION &&
|
||||
identifierUpdateNotification.getIdentifier().getForm() != Form.DECODER_TYPE &&
|
||||
identifierUpdateNotification.getIdentifier().getForm() != Form.CHANNEL_DESCRIPTOR)
|
||||
{
|
||||
if(identifierUpdateNotification.isAdd())
|
||||
{
|
||||
getIdentifierCollection().update(identifierUpdateNotification.getIdentifier());
|
||||
}
|
||||
else if(identifierUpdateNotification.isSilentAdd())
|
||||
{
|
||||
getIdentifierCollection().silentUpdate(identifierUpdateNotification.getIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
if(identifierUpdateNotification.getOperation() == IdentifierUpdateNotification.Operation.ADD)
|
||||
{
|
||||
Identifier identifier = identifierUpdateNotification.getIdentifier();
|
||||
|
||||
if(identifier instanceof ChannelDescriptorConfigurationIdentifier)
|
||||
{
|
||||
setCurrentChannel(((ChannelDescriptorConfigurationIdentifier)identifier).getValue());
|
||||
}
|
||||
else if(identifier instanceof IChannelDescriptor)
|
||||
{
|
||||
setCurrentChannel((IChannelDescriptor)identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user