001package jmri.jmrix.roco.z21;
002
003import java.util.ArrayList;
004import jmri.CollectingReporter;
005import jmri.DccLocoAddress;
006import jmri.InstanceManager;
007import jmri.RailCom;
008import jmri.RailComManager;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Z21CanReporter implements the Reporter Manager interface
014 * for Can connected reporters on Roco Z21 systems.
015 * <p>
016 * Reports from this reporter are of the type jmri.RailCom.
017 *
018 * @author Paul Bender Copyright (C) 2016
019 */
020public class Z21CanReporter extends jmri.implementation.AbstractRailComReporter implements Z21Listener,CollectingReporter {
021
022    private Z21SystemConnectionMemo _memo;
023
024    private int networkID=0; // CAN network ID associated with this reporter's module.
025    private int moduleAddress=-1; // User assigned address associated with this reporter's module.
026    private int port; // module port (0-7) associated with this reporter.
027
028    private ArrayList<Object> idTags;
029
030    /**
031     * Create a new Z21CanReporter.
032     *
033     * @param systemName the system name of the new reporter.
034     * @param userName the user name of the new reporter.
035     * @param memo an instance of Z21SystemConnectionMemo this reporter
036     *             is associated with.
037     *
038     */
039    public Z21CanReporter(String systemName,String userName,Z21SystemConnectionMemo memo){
040        super(systemName,userName);
041        _memo = memo;
042        _memo.getTrafficController().addz21Listener(this);
043        //Address format passed is in the form of moduleAddress:pin
044        try {
045            setIdentifiersFromSystemName(systemName);
046        } catch (NumberFormatException ex) {
047           log.debug("Unable to convert {} into the cab and input format of nn:xx",systemName);
048           throw new IllegalArgumentException("requires mm:pp format address.");
049        }
050        idTags = new ArrayList<>();
051        // request an update from the layout
052        //if(networkID!=0){
053        //leave commented out for now, causing loop that needs investigation.
054        //   _memo.getTrafficController().sendz21Message(Z21Message.getLanCanDetector(networkID),this);
055        //}
056    }
057
058    private void setIdentifiersFromSystemName(String systemName){
059        String moduleAddressText = Z21CanBusAddress.getEncoderAddressString(systemName,_memo.getSystemPrefix());
060        try{
061           moduleAddress = Integer.parseInt(moduleAddressText);
062        } catch (NumberFormatException ex) {
063           // didn't parse as a decimal, check to see if network ID
064           // was used instead.
065           networkID = Integer.parseInt(moduleAddressText,16);
066        }
067        port = Z21CanBusAddress.getBitFromSystemName(systemName,_memo.getSystemPrefix());
068    }
069
070    // the Z21 Listener interface
071
072    /**
073     * {@inheritDoc}
074     */
075    @Override
076    public void reply(Z21Reply msg){
077         // for incoming messages all the reporter cares about is
078         // LAN_CAN_DETECTOR messages.
079         if(msg.isCanReporterMessage()){
080            int netID = ( msg.getElement(4)&0xFF) + ((msg.getElement(5)&0xFF) << 8);
081            int address = ( msg.getElement(6)&0xFF) + ((msg.getElement(7)&0xFF) << 8);
082            int msgPort = ( msg.getElement(8) & 0xFF);
083            if(!messageForReporter(address,netID,msgPort)) {
084                return; // not our messge.
085            }
086            int type = ( msg.getElement(9) & 0xFF);
087            if(type==0x11) { // restart the list.
088               log.trace("clear list, size {}",idTags.size());
089               idTags.clear();
090               notify(null);
091            }
092            int value1 = (msg.getElement(10)&0xFF) + ((msg.getElement(11)&0xFF) << 8);
093            int value2 = (msg.getElement(12)&0xFF) + ((msg.getElement(13)&0xFF) << 8);
094            RailCom tag = getRailComTagFromValue(msg,value1);
095            if(tag != null ) {
096               log.trace("add tag {}",tag);
097               notify(tag);
098               idTags.add(tag);
099               // add the tag to the collection
100               tag = getRailComTagFromValue(msg,value2);
101               if(tag != null ) {
102                  log.trace("add tag {} ",tag);
103                  notify(tag);
104                  // add the tag to idTags
105                  idTags.add(tag);
106               }
107            }
108            if(log.isDebugEnabled()){
109               log.debug("after message, new list size {}",idTags.size());
110               int i = 0;
111               for(Object id:idTags){
112                  log.debug("tag {}: {}",i++,id);
113               }
114            }
115         }
116    }
117
118    private boolean messageForReporter(int address,int netId,int msgPort){
119        return (address == moduleAddress || netId == networkID) &&  msgPort == port;
120    }
121    /*
122     * private method to get and update a railcom tag based on the value
123     * bytes from the message.
124     */
125    private RailCom getRailComTagFromValue(Z21Reply msg,int value){
126       DccLocoAddress l = msg.getCanDetectorLocoAddress(value);
127       if (l != null ) { // 0 represents end of list or no railcom address.
128          // get the first locomotive address from the message.
129          log.debug("reporting tag for address 1 {}",l);
130          // see if there is a tag for this address.
131          RailCom tag = (RailCom) InstanceManager.getDefault(RailComManager.class).provideIdTag("" + l.getNumber());
132          int direction = (0xC000&value);
133          switch (direction) {
134             case 0x8000:
135                tag.setOrientation(RailCom.ORIENTA);
136                break;
137             case 0xC000:
138                tag.setOrientation(RailCom.ORIENTB);
139                break;
140             default:
141                tag.setOrientation(0);
142          }
143          return tag;
144       }
145       return null; // address in the message indicates end of list.
146    }
147
148    /**
149     * {@inheritDoc}
150     */
151    @Override
152    public void message(Z21Message msg){
153         // we don't need to handle outgoing messages, so just ignore them.
154    }
155
156    // the CollectingReporter interface.
157    /**
158     * {@inheritDoc}
159     */
160    @Override
161    public java.util.Collection<Object> getCollection(){
162        return idTags;
163    }
164
165    private static final Logger log = LoggerFactory.getLogger(Z21CanReporter.class);
166
167}