mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
controllers manager sample buffer processing and channel management. General cleanup across all modules to remove the executor from each of the start() methods. Removed the B100 tuner class since it's not yet implemented.
575 lines
20 KiB
Java
575 lines
20 KiB
Java
/*******************************************************************************
|
|
* sdrtrunk
|
|
* Copyright (C) 2014-2017 Dennis Sheirer
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
******************************************************************************/
|
|
package module.decode.passport;
|
|
|
|
import alias.Alias;
|
|
import alias.AliasList;
|
|
import alias.id.AliasIDType;
|
|
import channel.metadata.AliasedStringAttributeMonitor;
|
|
import channel.metadata.Attribute;
|
|
import channel.metadata.AttributeChangeRequest;
|
|
import channel.state.DecoderState;
|
|
import channel.state.DecoderStateEvent;
|
|
import channel.state.DecoderStateEvent.Event;
|
|
import channel.state.State;
|
|
import message.Message;
|
|
import module.decode.DecoderType;
|
|
import module.decode.event.CallEvent;
|
|
import module.decode.event.CallEvent.CallEventType;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.TreeSet;
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
|
|
public class PassportDecoderState extends DecoderState
|
|
{
|
|
private final static Logger mLog =
|
|
LoggerFactory.getLogger(PassportDecoderState.class);
|
|
|
|
private HashSet<String> mTalkgroupsFirstHeard = new HashSet<String>();
|
|
private TreeSet<String> mTalkgroups = new TreeSet<String>();
|
|
private TreeSet<String> mMobileIDs = new TreeSet<String>();
|
|
private HashMap<Integer,String> mSiteLCNs = new HashMap<Integer,String>();
|
|
private HashMap<Integer,String> mNeighborLCNs = new HashMap<Integer,String>();
|
|
|
|
private AliasedStringAttributeMonitor mFromMobileIDAttribute;
|
|
private AliasedStringAttributeMonitor mToTalkgroupAttribute;
|
|
private int mChannelNumber;
|
|
private int mSiteNumber;
|
|
private PassportBand mSiteBand;
|
|
private HashMap<Integer,String> mActiveCalls = new HashMap<Integer,String>();
|
|
private long mFrequency;
|
|
|
|
public PassportDecoderState(AliasList aliasList)
|
|
{
|
|
super(aliasList);
|
|
|
|
mFromMobileIDAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM,
|
|
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.MIN);
|
|
mToTalkgroupAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_TO,
|
|
getAttributeChangeRequestListener(), getAliasList(), AliasIDType.TALKGROUP);
|
|
}
|
|
|
|
@Override
|
|
public DecoderType getDecoderType()
|
|
{
|
|
return DecoderType.PASSPORT;
|
|
}
|
|
|
|
private void logTalkgroup(String talkgroup)
|
|
{
|
|
if(mTalkgroupsFirstHeard.contains(talkgroup))
|
|
{
|
|
mTalkgroups.add(talkgroup);
|
|
}
|
|
else
|
|
{
|
|
mTalkgroupsFirstHeard.add(talkgroup);
|
|
}
|
|
}
|
|
|
|
private PassportCallEvent getCurrentCallEvent()
|
|
{
|
|
return (PassportCallEvent) mCurrentCallEvent;
|
|
}
|
|
|
|
/**
|
|
* Indicates if the talkgroup is different than the talkgroup specified in
|
|
* the current call event
|
|
*/
|
|
private boolean isDifferentTalkgroup(String talkgroup)
|
|
{
|
|
return talkgroup != null &&
|
|
mCurrentCallEvent != null &&
|
|
mCurrentCallEvent.getToID() != null &&
|
|
!mCurrentCallEvent.getToID().contentEquals(talkgroup);
|
|
}
|
|
|
|
@Override
|
|
public void receive(Message message)
|
|
{
|
|
if(message instanceof PassportMessage)
|
|
{
|
|
PassportMessage passport = (PassportMessage) message;
|
|
|
|
if(passport.isValid())
|
|
{
|
|
switch(passport.getMessageType())
|
|
{
|
|
case CA_STRT:
|
|
mSiteLCNs.put(passport.getLCN(),
|
|
passport.getLCNFrequencyFormatted());
|
|
|
|
String talkgroup =
|
|
String.valueOf(passport.getTalkgroupID());
|
|
|
|
logTalkgroup(talkgroup);
|
|
|
|
if(mChannelNumber == 0)
|
|
{
|
|
setChannelNumber(passport.getLCN());
|
|
}
|
|
|
|
/* Call on this channel */
|
|
if(passport.getLCN() == mChannelNumber)
|
|
{
|
|
mToTalkgroupAttribute.process(talkgroup);
|
|
|
|
PassportCallEvent current = getCurrentCallEvent();
|
|
|
|
/* If we're already in a call event, add the message
|
|
* to the current call event ... if false, then we
|
|
* have a different call ... cleanup the old one. */
|
|
if(current != null && isDifferentTalkgroup(talkgroup))
|
|
{
|
|
mCurrentCallEvent.end();
|
|
mCurrentCallEvent = null;
|
|
}
|
|
|
|
if(mCurrentCallEvent == null)
|
|
{
|
|
mCurrentCallEvent = new PassportCallEvent
|
|
.Builder(CallEventType.CALL)
|
|
.aliasList(getAliasList())
|
|
.channel(String.valueOf(mChannelNumber))
|
|
.frequency(passport.getLCNFrequency())
|
|
.to(String.valueOf(passport.getTalkgroupID()))
|
|
.build();
|
|
|
|
broadcast(mCurrentCallEvent);
|
|
|
|
broadcast(new DecoderStateEvent(this,
|
|
Event.START, State.CALL));
|
|
}
|
|
else
|
|
{
|
|
broadcast(new DecoderStateEvent(this,
|
|
Event.CONTINUATION, State.CALL));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Call Detection
|
|
int lcn = passport.getLCN();
|
|
String tg = String.valueOf(passport.getTalkgroupID());
|
|
|
|
if(!mActiveCalls.containsKey(lcn) ||
|
|
!mActiveCalls.get(lcn).contentEquals(tg))
|
|
{
|
|
mActiveCalls.put(lcn, tg);
|
|
|
|
broadcast(new PassportCallEvent
|
|
.Builder(CallEventType.CALL_DETECT)
|
|
.aliasList(getAliasList())
|
|
.channel(String.valueOf(lcn))
|
|
.details("Site: " + passport.getSite())
|
|
.frequency(passport.getLCNFrequency())
|
|
.to(tg)
|
|
.build());
|
|
}
|
|
}
|
|
break;
|
|
case CA_ENDD:
|
|
String endTalkgroup =
|
|
String.valueOf(passport.getTalkgroupID());
|
|
|
|
logTalkgroup(endTalkgroup);
|
|
mToTalkgroupAttribute.process(endTalkgroup);
|
|
|
|
broadcast(new DecoderStateEvent(this,
|
|
Event.END, State.CALL));
|
|
|
|
if(mCurrentCallEvent != null)
|
|
{
|
|
mCurrentCallEvent.end();
|
|
broadcast(mCurrentCallEvent);
|
|
mCurrentCallEvent = null;
|
|
|
|
broadcast(new DecoderStateEvent(this, Event.RESET, State.FADE));
|
|
}
|
|
break;
|
|
case DA_STRT:
|
|
mSiteLCNs.put(passport.getLCN(),
|
|
passport.getLCNFrequencyFormatted());
|
|
|
|
String dataTalkgroup =
|
|
String.valueOf(passport.getTalkgroupID());
|
|
|
|
logTalkgroup(dataTalkgroup);
|
|
|
|
if(mChannelNumber == 0)
|
|
{
|
|
setChannelNumber(passport.getLCN());
|
|
}
|
|
|
|
/* Data call on this channel */
|
|
if(passport.getLCN() == mChannelNumber)
|
|
{
|
|
mToTalkgroupAttribute.process(dataTalkgroup);
|
|
|
|
PassportCallEvent current = getCurrentCallEvent();
|
|
|
|
/* If we're already in a call event, add the message
|
|
* to the current call event ... if false, then we
|
|
* have a different call ... cleanup the old one. */
|
|
if(current != null && isDifferentTalkgroup(dataTalkgroup))
|
|
{
|
|
mCurrentCallEvent.end();
|
|
mCurrentCallEvent = null;
|
|
}
|
|
|
|
if(mCurrentCallEvent == null)
|
|
{
|
|
mCurrentCallEvent = new PassportCallEvent
|
|
.Builder(CallEventType.DATA_CALL)
|
|
.aliasList(getAliasList())
|
|
.channel(String.valueOf(mChannelNumber))
|
|
.frequency(passport.getLCNFrequency())
|
|
.to(String.valueOf(passport.getTalkgroupID()))
|
|
.build();
|
|
|
|
broadcast(mCurrentCallEvent);
|
|
|
|
broadcast(new DecoderStateEvent(this,
|
|
Event.START, State.DATA));
|
|
}
|
|
else
|
|
{
|
|
broadcast(new DecoderStateEvent(this,
|
|
Event.CONTINUATION, State.DATA));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Call Detection
|
|
int lcn = passport.getLCN();
|
|
String tg = String.valueOf(passport.getTalkgroupID());
|
|
|
|
if(!mActiveCalls.containsKey(lcn) ||
|
|
!mActiveCalls.get(lcn).contentEquals(tg))
|
|
{
|
|
mActiveCalls.put(lcn, tg);
|
|
|
|
broadcast(new PassportCallEvent
|
|
.Builder(CallEventType.DATA_CALL)
|
|
.aliasList(getAliasList())
|
|
.channel(String.valueOf(lcn))
|
|
.details("Site: " + passport.getSite())
|
|
.frequency(passport.getLCNFrequency())
|
|
.to(tg)
|
|
.build());
|
|
}
|
|
}
|
|
break;
|
|
case DA_ENDD:
|
|
String dataEndTalkgroup =
|
|
String.valueOf(passport.getTalkgroupID());
|
|
|
|
logTalkgroup(dataEndTalkgroup);
|
|
mToTalkgroupAttribute.process(dataEndTalkgroup);
|
|
|
|
broadcast(new DecoderStateEvent(this,
|
|
Event.END, State.DATA));
|
|
|
|
if(mCurrentCallEvent != null)
|
|
{
|
|
mCurrentCallEvent.end();
|
|
broadcast(mCurrentCallEvent);
|
|
mCurrentCallEvent = null;
|
|
|
|
broadcast(new DecoderStateEvent(this, Event.RESET, State.FADE));
|
|
}
|
|
break;
|
|
case ID_RDIO:
|
|
String min = passport.getMobileID();
|
|
|
|
if(min != null)
|
|
{
|
|
mMobileIDs.add(min);
|
|
}
|
|
|
|
mFromMobileIDAttribute.process(min);
|
|
|
|
final CallEvent current = getCurrentCallEvent();
|
|
|
|
if(current != null)
|
|
{
|
|
current.setFromID(min);
|
|
broadcast(current);
|
|
|
|
}
|
|
break;
|
|
case RA_REGI:
|
|
if(mCurrentCallEvent == null ||
|
|
mCurrentCallEvent.getCallEventType() != CallEventType.REGISTER)
|
|
{
|
|
mCurrentCallEvent = new PassportCallEvent
|
|
.Builder(CallEventType.REGISTER)
|
|
.aliasList(getAliasList())
|
|
.channel(String.valueOf(passport.getLCN()))
|
|
.frequency(passport.getLCNFrequency())
|
|
.to(passport.getToID())
|
|
.build();
|
|
|
|
broadcast(mCurrentCallEvent);
|
|
|
|
broadcast(new DecoderStateEvent(this,
|
|
Event.START, State.DATA));
|
|
}
|
|
else
|
|
{
|
|
broadcast(new DecoderStateEvent(this,
|
|
Event.CONTINUATION, State.DATA));
|
|
}
|
|
break;
|
|
case SY_IDLE:
|
|
if(passport.getFree() != 0)
|
|
{
|
|
mNeighborLCNs.put(passport.getFree(),
|
|
passport.getFreeFrequencyFormatted());
|
|
}
|
|
setSiteNumber(passport.getSite());
|
|
|
|
PassportBand band = passport.getSiteBand();
|
|
setSiteBand(band);
|
|
setChannelNumber(getSiteBand().getChannel(mFrequency));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static String formatTalkgroup(String talkgroup)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
if(talkgroup.length() == 6)
|
|
{
|
|
sb.append(talkgroup.substring(0, 1));
|
|
sb.append("-");
|
|
sb.append(talkgroup.substring(1, 3));
|
|
sb.append("-");
|
|
sb.append(talkgroup.substring(3, 6));
|
|
|
|
return sb.toString();
|
|
}
|
|
else
|
|
{
|
|
return talkgroup;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getActivitySummary()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
sb.append("Activity Summary\n");
|
|
sb.append("Decoder:\tPassport\n\n");
|
|
sb.append("Site Channels\n");
|
|
|
|
if(mSiteLCNs.isEmpty())
|
|
{
|
|
sb.append(" None\n");
|
|
}
|
|
else
|
|
{
|
|
ArrayList<Integer> channels = new ArrayList<>(mSiteLCNs.keySet());
|
|
Collections.sort(channels);
|
|
|
|
for(Integer channel : channels)
|
|
{
|
|
sb.append(" " + channel);
|
|
sb.append("\t" + mSiteLCNs.get(channel));
|
|
sb.append("\n");
|
|
}
|
|
}
|
|
|
|
sb.append("\nNeighbor Channels\n");
|
|
|
|
if(mNeighborLCNs.isEmpty())
|
|
{
|
|
sb.append(" None\n");
|
|
}
|
|
else
|
|
{
|
|
ArrayList<Integer> channels = new ArrayList<>(mNeighborLCNs.keySet());
|
|
Collections.sort(channels);
|
|
|
|
for(Integer channel : channels)
|
|
{
|
|
sb.append(" " + channel);
|
|
sb.append("\t" + mNeighborLCNs.get(channel));
|
|
sb.append("\n");
|
|
}
|
|
}
|
|
|
|
sb.append("\nTalkgroups\n");
|
|
|
|
if(mTalkgroups.isEmpty())
|
|
{
|
|
sb.append(" None\n");
|
|
}
|
|
else
|
|
{
|
|
Iterator<String> it = mTalkgroups.iterator();
|
|
|
|
while(it.hasNext())
|
|
{
|
|
String tgid = it.next();
|
|
|
|
sb.append(" ");
|
|
sb.append(tgid);
|
|
sb.append(" ");
|
|
|
|
if(hasAliasList())
|
|
{
|
|
Alias alias = getAliasList().getTalkgroupAlias(tgid);
|
|
|
|
if(alias != null)
|
|
{
|
|
sb.append(alias.getName());
|
|
}
|
|
}
|
|
|
|
sb.append("\n");
|
|
}
|
|
}
|
|
|
|
sb.append("\nMobile ID Numbers\n");
|
|
|
|
if(mMobileIDs.isEmpty())
|
|
{
|
|
sb.append(" None\n");
|
|
}
|
|
else
|
|
{
|
|
Iterator<String> it = mMobileIDs.iterator();
|
|
|
|
while(it.hasNext())
|
|
{
|
|
String min = it.next();
|
|
|
|
sb.append(" ");
|
|
sb.append(min);
|
|
sb.append(" ");
|
|
|
|
if(hasAliasList())
|
|
{
|
|
Alias alias = getAliasList().getMobileIDNumberAlias(min);
|
|
|
|
if(alias != null)
|
|
{
|
|
sb.append(alias.getName());
|
|
}
|
|
}
|
|
|
|
sb.append("\n");
|
|
}
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
public void reset()
|
|
{
|
|
mTalkgroupsFirstHeard.clear();
|
|
mTalkgroups.clear();
|
|
mMobileIDs.clear();
|
|
mSiteLCNs.clear();
|
|
mNeighborLCNs.clear();
|
|
|
|
resetState();
|
|
}
|
|
|
|
private void resetState()
|
|
{
|
|
mToTalkgroupAttribute.reset();
|
|
mFromMobileIDAttribute.reset();
|
|
}
|
|
|
|
public int getChannelNumber()
|
|
{
|
|
return mChannelNumber;
|
|
}
|
|
|
|
private void setChannelNumber(int channel)
|
|
{
|
|
if(mChannelNumber != channel)
|
|
{
|
|
mChannelNumber = channel;
|
|
broadcast(new AttributeChangeRequest<String>(Attribute.CHANNEL_FREQUENCY_LABEL, "CHAN:" + mChannelNumber));
|
|
}
|
|
}
|
|
|
|
public int getSiteNumber()
|
|
{
|
|
return mSiteNumber;
|
|
}
|
|
|
|
private void setSiteNumber(int site)
|
|
{
|
|
if(mSiteNumber != site)
|
|
{
|
|
mSiteNumber = site;
|
|
Alias alias = hasAliasList() ? getAliasList().getSiteID(String.valueOf(getSiteNumber())) : null;
|
|
broadcast(new AttributeChangeRequest<String>(Attribute.NETWORK_ID_1, "SITE:" + mSiteNumber, alias));
|
|
}
|
|
}
|
|
|
|
private PassportBand getSiteBand()
|
|
{
|
|
return mSiteBand;
|
|
}
|
|
|
|
private void setSiteBand(PassportBand band)
|
|
{
|
|
if(mSiteBand == null)
|
|
{
|
|
mSiteBand = band;
|
|
broadcast(new AttributeChangeRequest<String>(Attribute.NETWORK_ID_2, "BAND:" + band.getDescription()));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void receiveDecoderStateEvent(DecoderStateEvent event)
|
|
{
|
|
switch(event.getEvent())
|
|
{
|
|
case RESET:
|
|
resetState();
|
|
break;
|
|
case SOURCE_FREQUENCY:
|
|
mFrequency = event.getFrequency();
|
|
broadcast(new AttributeChangeRequest<Long>(Attribute.CHANNEL_FREQUENCY, mFrequency));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|