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