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    /**
057     * {@inheritDoc}
058     */
059    @Override
060    public boolean hasExitReports() {
061        return true;
062    }
063
064    private void setIdentifiersFromSystemName(String systemName){
065        String moduleAddressText = Z21CanBusAddress.getEncoderAddressString(systemName,_memo.getSystemPrefix());
066        try{
067           moduleAddress = Integer.parseInt(moduleAddressText);
068        } catch (NumberFormatException ex) {
069           // didn't parse as a decimal, check to see if network ID
070           // was used instead.
071           networkID = Integer.parseInt(moduleAddressText,16);
072        }
073        port = Z21CanBusAddress.getBitFromSystemName(systemName,_memo.getSystemPrefix());
074    }
075
076    // the Z21 Listener interface
077
078    /**
079     * {@inheritDoc}
080     */
081    @Override
082    public void reply(Z21Reply msg){
083         // for incoming messages all the reporter cares about is
084         // LAN_CAN_DETECTOR messages.
085         if(msg.isCanReporterMessage()){
086            int netID = ( msg.getElement(4)&0xFF) + ((msg.getElement(5)&0xFF) << 8);
087            int address = ( msg.getElement(6)&0xFF) + ((msg.getElement(7)&0xFF) << 8);
088            int msgPort = ( msg.getElement(8) & 0xFF);
089            if(!messageForReporter(address,netID,msgPort)) {
090                return; // not our messge.
091            }
092            int type = ( msg.getElement(9) & 0xFF);
093            if(type==0x11) { // restart the list.
094               log.trace("clear list, size {}",idTags.size());
095               idTags.clear();
096               notify(null);
097            }
098            int value1 = (msg.getElement(10)&0xFF) + ((msg.getElement(11)&0xFF) << 8);
099            int value2 = (msg.getElement(12)&0xFF) + ((msg.getElement(13)&0xFF) << 8);
100            RailCom tag = getRailComTagFromValue(value1);
101            if(tag != null ) {
102               log.trace("add tag {}",tag);
103               notify(tag);
104               idTags.add(tag);
105               // add the tag to the collection
106               tag = getRailComTagFromValue(value2);
107               if(tag != null ) {
108                  log.trace("add tag {} ",tag);
109                  notify(tag);
110                  // add the tag to idTags
111                  idTags.add(tag);
112               }
113            }
114            if(log.isDebugEnabled()){
115               log.debug("after message, new list size {}",idTags.size());
116               int i = 0;
117               for(Object id:idTags){
118                  log.debug("tag {}: {}",i++,id);
119               }
120            }
121         }
122    }
123
124    private boolean messageForReporter(int address,int netId,int msgPort){
125        return (address == moduleAddress || netId == networkID) &&  msgPort == port;
126    }
127    /*
128     * private method to get and update a railcom tag based on the value
129     * bytes from the message.
130     */
131    private RailCom getRailComTagFromValue(int value){
132       DccLocoAddress l = Z21MessageUtils.getCanDetectorLocoAddress(value);
133       if (l != null ) { // 0 represents end of list or no railcom address.
134          // get the first locomotive address from the message.
135          log.debug("reporting tag for address 1 {}",l);
136          // see if there is a tag for this address.
137          RailCom tag = (RailCom) InstanceManager.getDefault(RailComManager.class).provideIdTag("" + l.getNumber());
138          int direction = (0xC000&value);
139          switch (direction) {
140             case 0x8000:
141                tag.setOrientation(RailCom.Orientation.EAST);
142                break;
143             case 0xC000:
144                tag.setOrientation(RailCom.Orientation.WEST);
145                break;
146             default:
147                tag.setOrientation(RailCom.Orientation.UNKNOWN);
148          }
149          return tag;
150       }
151       return null; // address in the message indicates end of list.
152    }
153
154    /**
155     * {@inheritDoc}
156     */
157    @Override
158    public void message(Z21Message msg){
159         // we don't need to handle outgoing messages, so just ignore them.
160    }
161
162    // the CollectingReporter interface.
163    /**
164     * {@inheritDoc}
165     */
166    @Override
167    public java.util.Collection<Object> getCollection(){
168        return idTags;
169    }
170
171    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Z21CanReporter.class);
172
173}