/******************************************************************************* * 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.mpt1327; 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.ChangeChannelTimeoutEvent; import channel.state.DecoderState; import channel.state.DecoderStateEvent; import channel.state.DecoderStateEvent.Event; import channel.state.State; import channel.traffic.TrafficChannelAllocationEvent; import controller.channel.Channel.ChannelType; import controller.channel.map.ChannelMap; import message.Message; import module.decode.DecoderType; import module.decode.config.DecodeConfiguration; import module.decode.event.CallEvent; import module.decode.event.CallEvent.CallEventType; import module.decode.mpt1327.MPT1327Message.IdentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import util.StringUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.TreeSet; import java.util.concurrent.ScheduledExecutorService; public class MPT1327DecoderState extends DecoderState { private final static Logger mLog = LoggerFactory.getLogger(MPT1327DecoderState.class); private TreeSet<String> mIdents = new TreeSet<String>(); private HashMap<String,ArrayList<String>> mGroups = new HashMap<String,ArrayList<String>>(); private String mSite; private AliasedStringAttributeMonitor mFromAttribute; private AliasedStringAttributeMonitor mToAttribute; private int mChannelNumber; private ChannelType mChannelType; private ChannelMap mChannelMap; private long mFrequency = 0; private long mCallTimeout; public MPT1327DecoderState(AliasList aliasList, ChannelMap channelMap, ChannelType channelType, long callTimeout) { super(aliasList); mChannelMap = channelMap; mChannelType = channelType; mCallTimeout = callTimeout; mFromAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_FROM, getAttributeChangeRequestListener(), getAliasList(), AliasIDType.MPT1327); mToAttribute = new AliasedStringAttributeMonitor(Attribute.PRIMARY_ADDRESS_TO, getAttributeChangeRequestListener(), getAliasList(), AliasIDType.MPT1327); } @Override public DecoderType getDecoderType() { return DecoderType.MPT1327; } @Override public void start(ScheduledExecutorService executor) { } @Override public void stop() { } public ChannelType getChannelType() { return mChannelType; } public void dispose() { super.dispose(); } @Override public void receive(Message message) { if(message.isValid()) { if(message instanceof MPT1327Message) { MPT1327Message mpt = ((MPT1327Message) message); switch(mpt.getMessageType()) { case ACK: mIdents.add(mpt.getFromID()); IdentType identType = mpt.getIdent1Type(); if(identType == IdentType.REGI) { broadcast(new MPT1327CallEvent.Builder(CallEventType.REGISTER) .aliasList(getAliasList()) .channel(String.valueOf(mChannelNumber)) .details("REGISTERED ON NETWORK") .frequency(mFrequency) .from(mpt.getToID()) .to(mpt.getFromID()) .build()); } else { broadcast(new MPT1327CallEvent.Builder(CallEventType.RESPONSE) .aliasList(getAliasList()) .channel(String.valueOf(mChannelNumber)) .details("ACK " + identType.getLabel()) .frequency(mFrequency) .from(mpt.getFromID()) .to(mpt.getToID()) .build()); } broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case ACKI: mIdents.add(mpt.getFromID()); mIdents.add(mpt.getToID()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case AHYC: mIdents.add(mpt.getToID()); broadcast(new MPT1327CallEvent.Builder(CallEventType.COMMAND) .aliasList(getAliasList()) .channel(String.valueOf(mChannelNumber)) .details(((MPT1327Message) message).getRequestString()) .frequency(mFrequency) .from(mpt.getFromID()) .to(mpt.getToID()) .build()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case AHYQ: broadcast(new MPT1327CallEvent.Builder(CallEventType.STATUS) .aliasList(getAliasList()) .channel(String.valueOf(mChannelNumber)) .details(mpt.getStatusMessage()) .frequency(mFrequency) .from(mpt.getFromID()) .to(mpt.getToID()) .build()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; case ALH: setSite(mpt.getSiteID()); broadcast(new DecoderStateEvent(this, Event.START, State.CONTROL)); break; case GTC: String from = mpt.getFromID(); String to = mpt.getToID(); if(from != null) { mIdents.add(from); } if(to != null) { mIdents.add(to); } /* Capture the idents that talk to each group */ if(from != null && to != null) { if(mGroups.containsKey(to)) { ArrayList<String> talkgroups = mGroups.get(to); if(!talkgroups.contains(from)) { talkgroups.add(from); } } else { ArrayList<String> talkgroups = new ArrayList<String>(); talkgroups.add(from); mGroups.put(to, talkgroups); } } int channel = mpt.getChannel(); long frequency = 0; if(getChannelMap() != null) { frequency = getChannelMap().getFrequency(channel); } CallEvent event = new MPT1327CallEvent.Builder(CallEventType.CALL) .aliasList(getAliasList()) .channel(String.valueOf(channel)) .details("GTC") .frequency(frequency) .from(mpt.getFromID()) .to(mpt.getToID()) .build(); broadcast(new TrafficChannelAllocationEvent(this, event)); break; case HEAD_PLUS1: case HEAD_PLUS2: case HEAD_PLUS3: case HEAD_PLUS4: broadcast(new MPT1327CallEvent.Builder(CallEventType.SDM) .aliasList(getAliasList()) .details(mpt.getMessage()) .from(mpt.getFromID()) .to(mpt.getToID()) .build()); broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.CONTROL)); break; /* Traffic Channel Events */ case CLEAR: mChannelNumber = mpt.getChannel(); broadcast(new DecoderStateEvent(this, Event.END, State.FADE)); break; case MAINT: if(mChannelType == ChannelType.STANDARD) { /** * When we receive a MAINT message and we're configured * as a standard channel, we need to apply the call * timeout specified by the user. Otherwise we'll * be using the shorter default call timeout */ broadcast(new ChangeChannelTimeoutEvent(this, mChannelType, mCallTimeout)); if(mCurrentCallEvent == null) { mCurrentCallEvent = new MPT1327CallEvent.Builder(CallEventType.CALL) .aliasList(getAliasList()) .channel(String.valueOf(mChannelNumber)) .details("MONITORED TRAFFIC CHANNEL") .frequency(mFrequency) .to(mpt.getToID()) .build(); broadcast(mCurrentCallEvent); } mToAttribute.process(mpt.getToID()); broadcast(new DecoderStateEvent(this, Event.START, State.CALL)); } break; default: break; } } } } public void reset() { mIdents.clear(); resetState(); } private void resetState() { mFromAttribute.reset(); mToAttribute.reset(); /** * If this is a standard channel, reset the fade timeout to the default * timeout. Once processing is underway, if we get a MAINT message, * this indicates we're processing a traffic channel as a standard * channel, so we'll issue a different call timeout then. */ if(mChannelType == ChannelType.STANDARD) { broadcast(new ChangeChannelTimeoutEvent(this, mChannelType, DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000)); if(mCurrentCallEvent != null) { mCurrentCallEvent.end(); broadcast(mCurrentCallEvent); mCurrentCallEvent = null; } } } public String getSite() { return mSite; } /** * Set the site number. */ public void setSite(String site) { if(!StringUtils.isEqual(mSite, site)) { mSite = site; broadcast(new AttributeChangeRequest<String>(Attribute.NETWORK_ID_1, "SITE:" + getSite(), getSiteAlias())); } } public Alias getSiteAlias() { if(hasAliasList()) { return getAliasList().getSiteID(mSite); } return null; } public ChannelMap getChannelMap() { return mChannelMap; } public void setChannelMap(ChannelMap channelMap) { mChannelMap = channelMap; } public int getChannelNumber() { return mChannelNumber; } /** * Set the channel number. This is used primarily for traffic channels since * the channel will already have been identified prior to the traffic * channel being created. */ public void setChannelNumber(int channel) { if(mChannelNumber != channel) { mChannelNumber = channel; broadcast(new AttributeChangeRequest<String>(Attribute.CHANNEL_FREQUENCY_LABEL, String.valueOf(mChannelNumber))); } } @Override public String getActivitySummary() { StringBuilder sb = new StringBuilder(); sb.append("Activity Summary - Decoder: MPT-1327\n"); sb.append(DIVIDER1); sb.append("Site: "); if(mSite != null) { sb.append(mSite); if(hasAliasList()) { Alias siteAlias = getAliasList().getSiteID(mSite); if(siteAlias != null) { sb.append(" ").append(siteAlias.getName()).append("\n"); } } } else { sb.append("Unknown\n"); } sb.append(DIVIDER2).append("Talkgroups: "); if(mGroups.isEmpty()) { sb.append("None\n"); } else { List<String> talkgroups = new ArrayList<String>(mGroups.keySet()); Collections.sort(talkgroups); for(String talkgroup : talkgroups) { sb.append("\n ").append(talkgroup); if(hasAliasList()) { Alias alias = getAliasList().getMPT1327Alias(talkgroup); if(alias != null) { sb.append(" "); sb.append(alias.getName()); } } sb.append("\n"); ArrayList<String> members = mGroups.get(talkgroup); Collections.sort(members); for(String member : members) { sb.append(" >"); sb.append(member); if(hasAliasList()) { Alias alias = getAliasList().getMPT1327Alias(member); if(alias != null) { sb.append(" "); sb.append(alias.getName()); } } sb.append("\n"); } } } sb.append(DIVIDER2).append("All Idents: "); if(mIdents.isEmpty()) { sb.append("None\n"); } else { sb.append("\n"); Iterator<String> it = mIdents.iterator(); while(it.hasNext()) { String ident = it.next(); sb.append(""); sb.append(ident); sb.append(" "); if(hasAliasList()) { Alias alias = getAliasList().getMPT1327Alias(ident); if(alias != null) { sb.append(alias.getName()); } } sb.append("\n"); } } return sb.toString(); } @Override public void init() { /* No initialization required */ } @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, event.getFrequency())); break; case TRAFFIC_CHANNEL_ALLOCATION: if(event.getSource() != MPT1327DecoderState.this) { if(event instanceof TrafficChannelAllocationEvent) { TrafficChannelAllocationEvent allocationEvent = (TrafficChannelAllocationEvent) event; String channel = allocationEvent.getCallEvent().getChannel(); if(channel != null) { try { setChannelNumber(Integer.valueOf(channel)); } catch(Exception e) { //Do nothing, we couldn't parse the channel number } } mFrequency = allocationEvent.getCallEvent().getFrequency(); broadcast(new AttributeChangeRequest<Long>(Attribute.CHANNEL_FREQUENCY, allocationEvent.getCallEvent().getFrequency())); mFromAttribute.process(allocationEvent.getCallEvent().getFromID()); mToAttribute.process(allocationEvent.getCallEvent().getToID()); } } break; default: break; } } }