#296 APCO25 Phase II Decoder Implementation (#589)

P25 Phase 2 Decoder implementation.
This commit is contained in:
Denny Sheirer
2019-11-17 06:56:52 -05:00
committed by GitHub
parent d5b802e794
commit f934676bd0
561 changed files with 35609 additions and 6215 deletions

View File

@@ -1,7 +1,7 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
* Copyright (C) 2014-2019 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,7 +19,7 @@
*/
package io.github.dsheirer.channel;
import io.github.dsheirer.module.decode.p25.message.IFrequencyBand;
import io.github.dsheirer.module.decode.p25.phase1.message.IFrequencyBand;
import io.github.dsheirer.protocol.Protocol;
/**

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* 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.metadata;
@@ -61,14 +63,31 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
private List<Alias> mFromIdentifierAliases;
private Identifier mToIdentifier;
private List<Alias> mToIdentifierAliases;
private Integer mTimeslot;
private IChannelMetadataUpdateListener mIChannelMetadataUpdateListener;
private AliasModel mAliasModel;
private AliasList mAliasList;
public ChannelMetadata(AliasModel aliasModel)
public ChannelMetadata(AliasModel aliasModel, Integer timeslot)
{
mAliasModel = aliasModel;
mTimeslot = timeslot;
}
public ChannelMetadata(AliasModel aliasModel)
{
this(aliasModel, null);
}
public Integer getTimeslot()
{
return mTimeslot;
}
public boolean hasTimeslot()
{
return mTimeslot != null;
}
@Override
@@ -242,6 +261,7 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
@Override
public void receive(IdentifierUpdateNotification update)
{
// mLog.debug("Received update: " + update + " class:" + update.getIdentifier().getClass());
Identifier identifier = update.getIdentifier();
switch(identifier.getIdentifierClass())

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* 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.metadata;
@@ -24,6 +26,7 @@ import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier;
import io.github.dsheirer.preference.PreferenceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,6 +34,7 @@ import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -77,7 +81,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
}
public void add(ChannelMetadata channelMetadata, Channel channel)
public void add(Collection<ChannelMetadata> channelMetadatas, Channel channel)
{
//Execute on the swing thread to avoid threading issues
EventQueue.invokeLater(new Runnable()
@@ -85,11 +89,14 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
@Override
public void run()
{
mChannelMetadata.add(channelMetadata);
mMetadataChannelMap.put(channelMetadata, channel);
int index = mChannelMetadata.indexOf(channelMetadata);
fireTableRowsInserted(index, index);
channelMetadata.setUpdateEventListener(ChannelMetadataModel.this);
for(ChannelMetadata channelMetadata: channelMetadatas)
{
mChannelMetadata.add(channelMetadata);
mMetadataChannelMap.put(channelMetadata, channel);
int index = mChannelMetadata.indexOf(channelMetadata);
fireTableRowsInserted(index, index);
channelMetadata.setUpdateEventListener(ChannelMetadataModel.this);
}
}
});
}
@@ -161,6 +168,8 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
case COLUMN_USER_FROM_ALIAS:
case COLUMN_USER_TO_ALIAS:
return Alias.class;
case COLUMN_DECODER_LOGICAL_CHANNEL_NAME:
return String.class;
default:
return Identifier.class;
}
@@ -180,7 +189,30 @@ public class ChannelMetadataModel extends AbstractTableModel implements IChannel
case COLUMN_DECODER_TYPE:
return channelMetadata.getDecoderTypeConfigurationIdentifier();
case COLUMN_DECODER_LOGICAL_CHANNEL_NAME:
return channelMetadata.getDecoderLogicalChannelNameIdentifier();
DecoderLogicalChannelNameIdentifier id = channelMetadata.getDecoderLogicalChannelNameIdentifier();
if(channelMetadata.hasTimeslot())
{
if(id == null)
{
return "TS:" + channelMetadata.getTimeslot();
}
else
{
return id.getValue() + " TS:" + channelMetadata.getTimeslot();
}
}
else
{
if(id == null)
{
return null;
}
else
{
return id.getValue();
}
}
case COLUMN_CONFIGURATION_FREQUENCY:
return channelMetadata.getFrequencyConfigurationIdentifier();
case COLUMN_CONFIGURATION_CHANNEL:

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* 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.metadata;
@@ -128,7 +130,7 @@ public class ChannelMetadataPanel extends JPanel implements ListSelectionListene
private void setColors()
{
mBackgroundColors.put(State.ACTIVE, Color.CYAN);
mForegroundColors.put(State.ACTIVE, Color.YELLOW);
mForegroundColors.put(State.ACTIVE, Color.BLUE);
mBackgroundColors.put(State.CALL, Color.BLUE);
mForegroundColors.put(State.CALL, Color.YELLOW);
mBackgroundColors.put(State.CONTROL, Color.ORANGE);

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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()
{

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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 );
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
},
/**

View 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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}
}
}