/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 Dennis Sheirer
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * (at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see
* * *****************************************************************************
*
*
*/
package io.github.dsheirer.controller.channel;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import io.github.dsheirer.controller.config.Configuration;
import io.github.dsheirer.module.decode.DecoderFactory;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceEvent;
import io.github.dsheirer.source.SourceType;
import io.github.dsheirer.source.config.SourceConfigFactory;
import io.github.dsheirer.source.config.SourceConfigRecording;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
@JacksonXmlRootElement(localName = "channel")
public class Channel extends Configuration implements Listener
{
private final static Logger mLog = LoggerFactory.getLogger(Channel.class);
// Standard channels are persisted and traffic channels are temporary
public enum ChannelType
{
STANDARD, TRAFFIC
}
// Static unique channel identifier tracking
private static int UNIQUE_ID = 0;
private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration();
private AuxDecodeConfiguration mAuxDecodeConfiguration = new AuxDecodeConfiguration();
private SourceConfiguration mSourceConfiguration = SourceConfigFactory.getDefaultSourceConfiguration();
private EventLogConfiguration mEventLogConfiguration = new EventLogConfiguration();
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
private StringProperty mAliasListName = new SimpleStringProperty();
private StringProperty mSystem = new SimpleStringProperty();
private StringProperty mSite = new SimpleStringProperty();
private StringProperty mName = new SimpleStringProperty();
private BooleanProperty mProcessing = new SimpleBooleanProperty();
private BooleanProperty mAutoStart = new SimpleBooleanProperty();
private IntegerProperty mAutoStartOrder = new SimpleIntegerProperty();
private boolean mSelected;
private TunerChannel mTunerChannel = null;
private ChannelType mChannelType = ChannelType.STANDARD;
private int mChannelID;
private int mChannelFrequencyCorrection = 0;
/**
* Channel represents a complete set of configurations needed to setup and
* manage a processing chain and/or manage as a set of business objects that
* can be displayed in graphical components.
*
* @param channelName
* @param channelType
*/
public Channel(String channelName, ChannelType channelType)
{
this(channelName);
mChannelType = channelType;
}
/**
* Constructs a new standard channel with the specified channel name
*/
public Channel(String channelName)
{
this();
mName.set(channelName);
}
/**
* Constructs a new standard channel with a default name of "Channel"
*/
public Channel()
{
mChannelID = UNIQUE_ID++;
}
/**
* Creates a (new) deep copy of this channel
*/
public Channel copyOf()
{
Channel channel = new Channel(mName.get());
channel.setSystem(mSystem.get());
channel.setSite(mSite.get());
channel.setAliasListName(mAliasListName.get());
channel.setAutoStart(mAutoStart.get());
channel.setAutoStartOrder(mAutoStartOrder.get());
AuxDecodeConfiguration auxCopy = new AuxDecodeConfiguration();
if(getAuxDecodeConfiguration() != null)
{
for(DecoderType auxType : getAuxDecodeConfiguration().getAuxDecoders())
{
auxCopy.addAuxDecoder(auxType);
}
}
channel.setAuxDecodeConfiguration(auxCopy);
channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
EventLogConfiguration logCopy = new EventLogConfiguration();
if(mEventLogConfiguration != null)
{
for(EventLogType logType : mEventLogConfiguration.getLoggers())
{
logCopy.addLogger(logType);
}
}
channel.setEventLogConfiguration(logCopy);
RecordConfiguration recordCopy = new RecordConfiguration();
if(mRecordConfiguration != null)
{
for(RecorderType recordType : mRecordConfiguration.getRecorders())
{
recordCopy.addRecorder(recordType);
}
}
channel.setRecordConfiguration(recordCopy);
channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
return channel;
}
/**
* Alias list name property
*/
public StringProperty aliasListNameProperty()
{
return mAliasListName;
}
/**
* System property
*/
public StringProperty systemProperty()
{
return mSystem;
}
/**
* Site property
*/
public StringProperty siteProperty()
{
return mSite;
}
/**
* Channel name property
*/
public StringProperty nameProperty()
{
return mName;
}
/**
* Processing property. Indicates if this channel configuration is currently processing.
*/
public BooleanProperty processingProperty()
{
return mProcessing;
}
/**
* Auto-Start property. Indicates if this channel is setup for auto-start
*/
public BooleanProperty autoStartProperty()
{
return mAutoStart;
}
/**
* Auto-start order property. Indicates the order for starting channels that are flagged for auto-start.
*/
public IntegerProperty autoStartOrderProperty()
{
return mAutoStartOrder;
}
/**
* Unique identifier for this channel. Value is transient (not persisted).
*/
@JsonIgnore
public int getChannelID()
{
return mChannelID;
}
/**
* Indicates if this channel has been selected for prioritized audio output
* and display of processing chain products
*/
@JsonIgnore
public boolean isSelected()
{
return mSelected;
}
/**
* Sets selection status of the channel.
*
* NOTE: this method is package private so that only the ChannelSelectionManager
* can toggle the selection state. Use the channel model to broadcast a
* request select/deselect channel event.
*
* @param selected
*/
void setSelected(boolean selected)
{
mSelected = selected;
}
@JsonIgnore
public ChannelType getChannelType()
{
return mChannelType;
}
@JsonIgnore
public boolean isTrafficChannel()
{
return mChannelType == ChannelType.TRAFFIC;
}
@JsonIgnore
public boolean isStandardChannel()
{
return mChannelType == ChannelType.STANDARD;
}
/**
* Returns the owning system for this channel.
*/
@JacksonXmlProperty(isAttribute = true, localName = "system")
public String getSystem()
{
return mSystem.get();
}
public void setSystem(String system)
{
mSystem.set(system);
}
public boolean hasSystem()
{
return mSystem != null && mSystem.get() != null;
}
/**
* Returns the owning site for this channel.
*/
@JacksonXmlProperty(isAttribute = true, localName = "site")
public String getSite()
{
return mSite.get();
}
public void setSite(String site)
{
mSite.set(site);
}
public boolean hasSite()
{
return mSite != null && mSite.get() != null;
}
/**
* Returns the name of this channel.
*/
@JacksonXmlProperty(isAttribute = true, localName = "name")
public String getName()
{
return mName.get();
}
/**
* Sets the name of this channel.
*/
public void setName(String name)
{
mName.set(name);
}
/**
* Default display string for this channel: SYSTEM_SITE_NAME
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(hasSystem() ? getSystem() : "SYSTEM");
sb.append("_");
sb.append(hasSite() ? getSite() : "SITE");
sb.append("_");
sb.append(getName());
return sb.toString();
}
/**
* Indicates if this channel is currently started and processing.
*/
@JsonIgnore
public boolean isProcessing()
{
return mProcessing.get();
}
/**
* Indicates if this channel is currently started and processing.
*
* Note: this method is package private and is intended to be managed by a channel processing manager instance
*
* @see ChannelProcessingManager
*/
void setProcessing(boolean processing)
{
mProcessing.set(processing);
}
/**
* Indicates if this channel has auto-start enabled.
*
* Note: the Json (de)serialization tag for auto-start is 'enabled'. We've re-purposed the now
* transient enabled property for the auto-start persisted value to maintain backward compatibility.
*
* @return true if this channel should be auto-started on application startup
*/
@JacksonXmlProperty(isAttribute = true, localName = "enabled")
public boolean getAutoStart()
{
return mAutoStart.get();
}
/**
* Indicates if this channel is set for auto-start
*/
@JsonIgnore
public boolean isAutoStart()
{
return mAutoStart.get();
}
/**
* Sets the auto-start status for this channel.
*
* @param autoStart
*/
public void setAutoStart(boolean autoStart)
{
mAutoStart.set(autoStart);
}
/**
* Auto-start order. Specifies an optional ordering for channels so that when the application starts up,
* the channels can be started in a user-specified order.
*
* @return auto-start order or null
*/
@JacksonXmlProperty(isAttribute = true, localName = "order")
public Integer getAutoStartOrder()
{
return mAutoStartOrder.get();
}
/**
* Sets the auto-start order for this channel
*
* @param order
*/
public void setAutoStartOrder(Integer order)
{
if(order != null)
{
mAutoStartOrder.set(order);
}
else
{
mAutoStartOrder.setValue(null);
}
}
/**
* Indicates if this channel has an auto-start order specified (ie non-null).
*
* @return true if this channel has an auto-start order
*/
@JsonIgnore
public boolean hasAutoStartOrder()
{
return mAutoStartOrder != null && mAutoStartOrder.getValue() != null;
}
/**
* Returns the alias list that is used by this channel for looking up alias
* values for the various identifiers produced by the decoder(s).
*/
@JacksonXmlProperty(isAttribute = false, localName = "alias_list_name")
public String getAliasListName()
{
return mAliasListName.get();
}
/**
* Sets the alias list to be used for looking up the alias values for the
* various identifiers produced by the decoder
*/
public void setAliasListName(String name)
{
mAliasListName.set(name);
}
/**
* Gets the primary decoder configuration used by this channel
*/
@JacksonXmlProperty(isAttribute = false, localName = "decode_configuration")
public DecodeConfiguration getDecodeConfiguration()
{
return mDecodeConfiguration;
}
/**
* Sets the primary decoder configuration used by this channel
*/
public void setDecodeConfiguration(DecodeConfiguration config)
{
if(config != null)
{
mDecodeConfiguration = config;
}
}
/**
* Gets the aux decoder configuration used by this channel. Aux decoders
* operate on demodulated audio
*/
@JacksonXmlProperty(isAttribute = false, localName = "aux_decode_configuration")
public AuxDecodeConfiguration getAuxDecodeConfiguration()
{
return mAuxDecodeConfiguration;
}
/**
* Sets the decoder configuration used by this channel
*/
public void setAuxDecodeConfiguration(AuxDecodeConfiguration config)
{
if(config != null)
{
mAuxDecodeConfiguration = config;
}
}
/**
* Returns the source configuration for this channel. A channel source
* identifies where the processing chain will get source sample data from.
*/
@JacksonXmlProperty(isAttribute = false, localName = "source_configuration")
public SourceConfiguration getSourceConfiguration()
{
return mSourceConfiguration;
}
/**
* Sets the source configuration for this channel.
*/
public void setSourceConfiguration(SourceConfiguration config)
{
if(config != null)
{
mSourceConfiguration = config;
}
//Clear the tune channel object so that it can be recreated if the
//source configuration changes
mTunerChannel = null;
}
/**
* Returns the event logger configuration for this channel.
*/
@JacksonXmlProperty(isAttribute = false, localName = "event_log_configuration")
public EventLogConfiguration getEventLogConfiguration()
{
return mEventLogConfiguration;
}
/**
* Sets the event logger configuration for this channel.
*/
public void setEventLogConfiguration(EventLogConfiguration config)
{
if(config != null)
{
mEventLogConfiguration = config;
}
}
/**
* Returns the recorder configuration for this channel.
*/
@JacksonXmlProperty(isAttribute = false, localName = "record_configuration")
public RecordConfiguration getRecordConfiguration()
{
return mRecordConfiguration;
}
/**
* Sets the recorder configuration for this channel.
*/
public void setRecordConfiguration(RecordConfiguration config)
{
if(config != null)
{
mRecordConfiguration = config;
}
}
/**
* Convenience method to construct a tuner channel object representing a
* tuner or recording source frequency and bandwidth that can be used by
* application components for graphically representing this channel.
*
* If the source configuration is not a tuner or recording, this method
* returns null.
*/
@JsonIgnore
public TunerChannel getTunerChannel()
{
if(mTunerChannel == null)
{
if(mSourceConfiguration.getSourceType() == SourceType.TUNER)
{
SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING)
{
SourceConfigRecording config = (SourceConfigRecording)mSourceConfiguration;
mTunerChannel = new TunerChannel(config.getFrequency(),
mDecodeConfiguration.getChannelSpecification().getBandwidth());
}
}
return mTunerChannel;
}
/**
* Convenience method to indicate if any part of this channel is contained
* within the minimum and maximum frequency values.
*/
public boolean isWithin(long minimum, long maximum)
{
TunerChannel tunerChannel = getTunerChannel();
return tunerChannel != null && tunerChannel.overlaps(minimum, maximum);
}
/**
* Convenience method to make the current channel frequency correction value
* available for use outside of a constructed processing chain while maintaining
* linkage to the source channel. This hack allows the correction value to
* be used to visually show in the spectral display.
*/
@Override
public void receive(SourceEvent event)
{
if(event.getEvent() == SourceEvent.Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE)
{
mChannelFrequencyCorrection = event.getValue().intValue();
}
}
/**
* Current channel frequency correction value (when this channel is processing)
*/
@JsonIgnore
public int getChannelFrequencyCorrection()
{
return mChannelFrequencyCorrection;
}
/**
* Resets frequency correction value to 0
*/
public void resetFrequencyCorrection()
{
mChannelFrequencyCorrection = 0;
}
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(o == null || getClass() != o.getClass())
{
return false;
}
Channel channel = (Channel)o;
return getChannelID() == channel.getChannelID();
}
@Override
public int hashCode()
{
return Objects.hash(getChannelID());
}
/**
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
*/
public static Callback extractor()
{
return (Channel c) -> new Observable[] {c.processingProperty(), c.nameProperty(), c.aliasListNameProperty(),
c.autoStartOrderProperty(), c.autoStartProperty(), c.siteProperty(), c.systemProperty()};
}
}