001package jmri.jmrix.roco.z21;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import jmri.jmrix.AbstractMRReply;
005import jmri.DccLocoAddress;
006
007/**
008 * Class for replies in the z21/Z21 protocol.
009 * <p>
010 * Replies are of the format: 2 bytes length 2 bytes opcode n bytes data
011 * <p>
012 * numeric data is sent in little endian format.
013 *
014 * @author Bob Jacobsen Copyright (C) 2003
015 * @author Paul Bender Copyright (C) 2014
016 */
017public class Z21Reply extends AbstractMRReply {
018
019    private static final String WRONG_REPLY_TYPE = "Wrong Reply Type";
020
021    /**
022     *  Create a new one.
023     */
024    public Z21Reply() {
025        super();
026        setBinary(true);
027    }
028
029    /**
030     * This ctor interprets the byte array as a sequence of characters to send.
031     *
032     * @param a Array of bytes to send.
033     * @param l length of reply.
034     */
035    public Z21Reply(byte[] a, int l) {
036        super();
037        _nDataChars = l;
038        setBinary(true);
039        for (int i = 0; i < _nDataChars; i++) {
040            _dataChars[i] = a[i];
041        }
042    }
043
044    // keep track of length
045    @Override
046    public void setElement(int n, int v) {
047        _dataChars[n] = (char) v;
048        _nDataChars = Math.max(_nDataChars, n + 1);
049    }
050
051    /**
052     * Get an integer representation of a BCD value.
053     *
054     * @param n byte in message to convert
055     * @return Integer value of BCD byte.
056     */
057    public Integer getElementBCD(int n) {
058        return Integer.decode(Integer.toHexString(getElement(n)));
059    }
060
061    @Override
062    public void setOpCode(int i) {
063        _dataChars[2] = (char) (i & 0x00ff);
064        _dataChars[3] = (char) ((i & 0xff00) >> 8);
065        _nDataChars = Math.max(_nDataChars, 4);  //smallest reply is of length 4.
066    }
067
068    @Override
069    public int getOpCode() {
070        return (0xff&_dataChars[2]) + ((0xff&_dataChars[3]) << 8);
071    }
072
073    public void setLength(int i) {
074        _dataChars[0] = (char) (i & 0x00ff);
075        _dataChars[1] = (char) ((i & 0xff00) >> 8);
076        _nDataChars = Math.max(_nDataChars, i);
077    }
078
079    public int getLength() {
080        return (0xff & _dataChars[0] ) + ((0xff & _dataChars[1]) << 8);
081    }
082
083    @Override
084    protected int skipPrefix(int index) {
085        return 0;
086    }
087
088    @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH")
089    @Override
090    public String toMonitorString() {
091        switch(getOpCode()){
092           case 0x0010:
093               int serialNo = (getElement(4)&0xff) + ((getElement(5)&0xff) << 8)
094                        + ((getElement(6)&0xff) << 16) + ((getElement(7)&0xff) << 24);
095               return Bundle.getMessage("Z21ReplyStringSerialNo", serialNo);
096           case 0x001A:
097               int hwversion = getElement(4) + (getElement(5) << 8) +
098                         (getElement(6) << 16 ) + (getElement(7) << 24 );
099               float swversion = (getElementBCD(8)/100.0f)+
100                               (getElementBCD(9))+
101                               (getElementBCD(10)*100)+
102                               (getElementBCD(11))*10000;
103               return Bundle.getMessage("Z21ReplyStringVersion",java.lang.Integer.toHexString(hwversion), swversion);
104           case 0x0040:
105               return Bundle.getMessage("Z21XpressNetTunnelReply", getXNetReply().toMonitorString());
106           case 0x0051:
107                return Bundle.getMessage("Z21ReplyBroadcastFlags",Z21MessageUtils.interpretBroadcastFlags(_dataChars));
108           case 0x0080:
109               int groupIndex = getElement(4) & 0xff;
110               int offset = (groupIndex * 10) + 1;
111               String[] moduleStatus = new String[10];
112               for(int i=0;i<10;i++){
113                  moduleStatus[i]= Bundle.getMessage("RMModuleFeedbackStatus",offset + i,
114                      Bundle.getMessage("RMModuleContactStatus",1, ((getElement(i+5)&0x01)==0x01)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")),
115                      Bundle.getMessage("RMModuleContactStatus",2, ((getElement(i+5)&0x02)==0x02)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")),
116                      Bundle.getMessage("RMModuleContactStatus",3, ((getElement(i+5)&0x04)==0x04)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")),
117                      Bundle.getMessage("RMModuleContactStatus",4, ((getElement(i+5)&0x08)==0x08)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")),
118                      Bundle.getMessage("RMModuleContactStatus",5, ((getElement(i+5)&0x10)==0x10)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")),
119                      Bundle.getMessage("RMModuleContactStatus",6, ((getElement(i+5)&0x20)==0x20)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")),
120                      Bundle.getMessage("RMModuleContactStatus",7, ((getElement(i+5)&0x40)==0x40)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")),
121                      Bundle.getMessage("RMModuleContactStatus",8, ((getElement(i+5)&0x80)==0x80)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")));
122               }
123               return Bundle.getMessage("RMBusFeedbackStatus",groupIndex,
124                      moduleStatus[0],moduleStatus[1],moduleStatus[2],
125                      moduleStatus[3],moduleStatus[4],moduleStatus[5],
126                      moduleStatus[6],moduleStatus[7],moduleStatus[8],
127                      moduleStatus[9]);
128           case 0x0084:
129               int mainCurrent = getSystemDataMainCurrent();
130               int progCurrent = getSystemDataProgCurrent();
131               int filteredMainCurrent = getSystemDataFilteredMainCurrent();
132               int temperature = getSystemDataTemperature();
133               int supplyVolts = getSystemDataSupplyVoltage();
134               int internalVolts = getSystemDataVCCVoltage();
135               int state = getElement(16);
136               int extendedState = getElement(17);
137               // data bytes 14 and 15 (offset 18 and 19) are reserved.
138               return Bundle.getMessage("Z21SystemStateReply",mainCurrent,
139                      progCurrent,filteredMainCurrent,temperature,
140                      supplyVolts,internalVolts,state,extendedState);
141           case 0x00A0:
142               return Bundle.getMessage("Z21LocoNetRxReply", getLocoNetMessage().toMonitorString());
143           case 0x00A1:
144               return Bundle.getMessage("Z21LocoNetTxReply", getLocoNetMessage().toMonitorString());
145           case 0x00A2:
146               return Bundle.getMessage("Z21LocoNetLanReply", getLocoNetMessage().toMonitorString());
147           case 0x0088:
148               int entries = getNumRailComDataEntries();
149               StringBuilder datastring = new StringBuilder();
150               for(int i = 0; i < entries ; i++) {
151                   DccLocoAddress address = getRailComLocoAddress(i);
152                   int rcvCount = getRailComRcvCount(i);
153                   int errorCount = getRailComErrCount(i);
154                   int speed = getRailComSpeed(i);
155                   int options = getRailComOptions(i);
156                   int qos = getRailComQos(i);
157                   datastring.append(Bundle.getMessage("Z21_RAILCOM_DATA",address,rcvCount,errorCount,options,speed,qos));
158                   datastring.append("\n");
159               }
160               return Bundle.getMessage("Z21_RAILCOM_DATACHANGED",entries,new String(datastring));
161           case 0x00C4:
162               int networkID = ( getElement(4)&0xFF) + ((getElement(5)&0xFF) << 8);
163               int address = ( getElement(6)&0xFF) + ((getElement(7)&0xFF) << 8);
164               int port = ( getElement(8) & 0xFF);
165               int type = ( getElement(9) & 0xFF);
166               int value1 = (getElement(10)&0xFF) + ((getElement(11)&0xFF) << 8);
167               int value2 = (getElement(12)&0xFF) + ((getElement(13)&0xFF) << 8);
168               String typeString = "";
169               String value1String;
170               String value2String = "";
171               switch(type){
172                    case 0x01:
173                         typeString = "Input Status";
174                         switch(value1){
175                           case 0x0000:
176                                value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_FREE_WITHOUT");
177                                break;
178                           case 0x0100:
179                                value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_FREE_WITH");
180                                break;
181                           case 0x1000:
182                                value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_BUSY_WITHOUT");
183                                break;
184                           case 0x1100:
185                                value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_BUSY_WITH");
186                                break;
187                           case 0x1201:
188                                value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_OVERLOAD_1");
189                                break;
190                           case 0x1202:
191                                value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_OVERLOAD_2");
192                                break;
193                           case 0x1203:
194                                value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_OVERLOAD_3");
195                                break;
196                           default:
197                                value1String = "<unknown>";
198                         }
199                         break;
200                    case 0x11: 
201                    case 0x12: 
202                    case 0x13: 
203                    case 0x14: 
204                    case 0x15: 
205                    case 0x16: 
206                    case 0x17: 
207                    case 0x18: 
208                    case 0x19: 
209                    case 0x1A: 
210                    case 0x1B: 
211                    case 0x1C: 
212                    case 0x1D: 
213                    case 0x1E: 
214                    case 0x1F:
215                         typeString = "Occupancy Info";
216                         value1String = getCanDetectorLocoAddressString(value1);
217                         value2String = getCanDetectorLocoAddressString(value2);
218                         break;
219                    default:
220                         value1String = "" + value1;
221                         value2String = "" + value2;
222               }
223                 
224               return Bundle.getMessage("Z21CANDetectorReply",Integer.toHexString(networkID),address,port,typeString,value1String,value2String);
225
226           default:
227        }
228
229        return toString();
230    }
231
232    // handle XpressNet replies tunneled in Z21 messages
233    boolean isXPressNetTunnelMessage() {
234        return (getOpCode() == 0x0040);
235    }
236
237    Z21XNetReply getXNetReply() {
238        Z21XNetReply xnr = null;
239        if (isXPressNetTunnelMessage()) {
240            int i = 4;
241            xnr = new Z21XNetReply();
242            for (; i < getLength(); i++) {
243                xnr.setElement(i - 4, getElement(i));
244            }
245            if(( xnr.getElement(0) & 0x0F ) > ( xnr.getNumDataElements()+2) ){
246               // there is at least one message from the Z21 that can be sent 
247               // with fewer bytes than the XpressNet payload indicates it
248               // should have.  Pad those messages with 0x00 bytes.
249               for(i=i-4;i<((xnr.getElement(0)&0x0F)+2);i++){
250                  xnr.setElement(i,0x00);
251               }
252            }
253        }
254        return xnr;
255    }
256   
257    // handle RailCom data replies
258    boolean isRailComDataChangedMessage(){
259        return (getOpCode() == 0x0088);
260    }
261
262    /**
263     * @return the number of RailCom entries in this message.
264     *         the returned value is in the 0 to 19 range.
265     */
266    int getNumRailComDataEntries(){
267        if(!this.isRailComDataChangedMessage()){
268           return 0; // this isn't a RailCom message, so there are no entries.
269        }
270        // if this is a RailCom message, the length field is
271        // then the entries are n=(len-4)/13, per the Z21 protocol 
272        // manual, section 8.1.  Also, 0<=n<=19
273        return ((getLength() - 4)/13);
274    } 
275
276    /**
277     * Get a locomotive address from an entry in a railcom message.
278     *
279     * @param n the entry to get the address from.
280     * @return the locomotive address for the specified entry.
281     */
282    DccLocoAddress getRailComLocoAddress(int n){
283         int offset = 4+(n*13);  // +4 to get past header
284         int address = Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset);
285         return new DccLocoAddress(address,address>=100);
286    }
287
288    /**
289     * Get the receive counter from an entry in a railcom message.
290     *
291     * @param n the entry to get the address from.
292     * @return the receive counter for the specified entry.
293     */
294    int getRailComRcvCount(int n){
295         int offset = 6+(n*13); // +6 to get header and address.
296         return ((0xff&getElement(offset+3))<<24) +
297                       ((0xff&(getElement(offset+2))<<16) + 
298                       ((0xff&getElement(offset+1))<<8) + 
299                       (0xff&(getElement(offset))));
300    }
301
302    /**
303     * Get the error counter from an entry in a railcom message.
304     *
305     * @param n the entry to get the address from.
306     * @return the error counter for the specified entry.
307     */
308    int getRailComErrCount(int n){
309         int offset = 10+(n*13); // +10 to get past header, address,and rcv count.
310         return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset);
311    }
312
313    /**
314     * Get the speed value from an entry in a railcom message.
315     *
316     * @param n the entry to get the address from.
317     * @return the error counter for the specified entry.
318     */
319    int getRailComSpeed(int n){
320         int options = getRailComOptions(n);
321         if(((options & 0x01) == 0x01) || ((options & 0x02) == 0x02)) { 
322            int offset = 14+(n*13); //+14 to get past the options, 
323                                    // and everything before the options.
324            return (0xff&(getElement(offset)));
325         } else {
326            return 0;
327         }
328    }
329
330    /**
331     * Get the options value from an entry in a railcom message.
332     *
333     * @param n the entry to get the address from.
334     * @return the options for the specified entry.
335     */
336    int getRailComOptions(int n){
337         int offset = 13+(n*13); //+13 to get past the header, address, rcv 
338                                 // counter, and reserved byte.
339         return (0xff&(getElement(offset)));
340    }
341
342    /**
343     * Get the Quality of Service value from an entry in a railcom message.
344     *
345     * @param n the entry to get the address from.
346     * @return the Quality of Service value for the specified entry.
347     */
348    int getRailComQos(int n){
349         if((getRailComOptions(n) & 0x04) == 0x04 ) { 
350            int offset = 15+(n*13); //+15 to get past the speed, 
351                                    // and everything before the speed.
352            return (0xff&(getElement(offset)));
353         } else {
354            return 0; // if the QOS bit isn't set, there is no QOS attribute.
355         }
356    }
357
358    // handle System data replies
359    boolean isSystemDataChangedReply(){
360        return (getOpCode() == 0x0084);
361    }
362
363    private void checkSystemDataChangeReply(){
364        if(!isSystemDataChangedReply()){
365            throw new IllegalArgumentException(WRONG_REPLY_TYPE);
366        }
367    }
368
369    /**
370     * Get the Main Track Current from the SystemStateDataChanged 
371     * message.
372     *
373     * @return the current in mA.
374     */
375    int getSystemDataMainCurrent(){
376         checkSystemDataChangeReply();
377         int offset = 4; //skip the headers
378         return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset);
379    }
380
381    /**
382     * Get the Programming Track Current from the SystemStateDataChanged 
383     * message.
384     *
385     * @return the current in mA.
386     */
387    int getSystemDataProgCurrent(){
388         checkSystemDataChangeReply();
389         int offset = 6; //skip the headers
390         return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset);
391    }
392
393    /**
394     * Get the Filtered Main Track Current from the SystemStateDataChanged 
395     * message.
396     *
397     * @return the current in mA.
398     */
399    int getSystemDataFilteredMainCurrent(){
400         checkSystemDataChangeReply();
401         int offset = 8; //skip the headers
402         return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset);
403    }
404
405    /**
406     * Get the Temperature from the SystemStateDataChanged 
407     * message.
408     *
409     * @return the current in degrees C.
410     */
411    int getSystemDataTemperature(){
412         checkSystemDataChangeReply();
413         int offset = 10; //skip the headers
414         return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset);
415    }
416
417    /**
418     * Get the Supply Voltage from the SystemStateDataChanged 
419     * message.
420     *
421     * @return the current in mV.
422     */
423    int getSystemDataSupplyVoltage(){
424         checkSystemDataChangeReply();
425         int offset = 12; //skip the headers
426         return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset);
427    }
428
429    /**
430     * Get the VCC (and track) Voltage from the SystemStateDataChanged 
431     * message.
432     *
433     * @return the current in mV.
434     */
435    int getSystemDataVCCVoltage(){
436         checkSystemDataChangeReply();
437         int offset = 14; //skip the headers
438         return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset);
439    }
440
441    // handle LocoNet replies tunneled in Z21 messages
442    boolean isLocoNetTunnelMessage() {
443        switch (getOpCode()){
444          case 0xA0: // LAN_LOCONET_Z21_RX
445          case 0xA1: // LAN_LOCONET_Z21_TX
446          case 0xA2: // LAN_LOCONET_FROM_LAN
447             return true;
448          default:
449             return false;
450        }
451    }
452
453    boolean isLocoNetDispatchMessage() {
454       return (getOpCode() == 0xA3);
455    }
456
457    boolean isLocoNetDetectorMessage() {
458       return (getOpCode() == 0xA4);
459    }
460
461    jmri.jmrix.loconet.LocoNetMessage getLocoNetMessage() {
462        jmri.jmrix.loconet.LocoNetMessage lnr = null;
463        if (isLocoNetTunnelMessage()) {
464            int i = 4;
465            lnr = new jmri.jmrix.loconet.LocoNetMessage(getLength()-4);
466            for (; i < getLength(); i++) {
467                lnr.setElement(i - 4, getElement(i));
468            }
469        }
470        return lnr;
471    }
472
473    // handle RMBus data replies
474    boolean isRMBusDataChangedReply(){
475        return (getOpCode() == 0x0080);
476    }
477
478    // handle CAN Feedback/Railcom replies
479    boolean isCanDetectorMessage() {
480        return (getOpCode() == 0x00C4);
481    }
482
483    // address value is the 16 bits of the two bytes containing the
484    // address.  The most significan two bits represent the direction.
485    String getCanDetectorLocoAddressString(int addressValue) {
486        if(!isCanDetectorMessage()) {
487           return "";
488        }
489        String addressString;
490        if(addressValue==0) {
491           addressString="end of list";
492        } else {
493           addressString = "" + (getCanDetectorLocoAddress(addressValue)).toString();
494           int direction = (0xC000&addressValue);
495           switch (direction) {
496              case 0x8000:
497                 addressString += " direction forward";
498                 break;
499              case 0xC000:
500                 addressString += " direction reverse";
501                 break;
502              default:
503                 addressString += " direction unknown";
504          }
505       }
506       return addressString;
507   }
508
509    // address value is the 16 bits of the two bytes containing the
510    // address.  The most significant two bits represent the direction.
511    DccLocoAddress getCanDetectorLocoAddress(int addressValue) {
512        if(!isCanDetectorMessage()) {
513           return null;
514        }
515        if(addressValue==0) {
516           return null;
517        } else {
518           int locoAddress = (0x3FFF&addressValue);
519           return new DccLocoAddress(locoAddress,locoAddress>=100);
520        }
521   }
522
523    /**
524     * @return the can Detector Message type or -1 if not a can detector message.
525     */
526   public int canDetectorMessageType() {
527        if(isCanDetectorMessage()){
528            return getElement(9) & 0xFF;
529        }
530        return -1;
531   }
532
533    /**
534      * @return true if the reply is for a CAN detector and the type is 0x01
535     */
536   public boolean isCanSensorMessage(){
537        return isCanDetectorMessage() && canDetectorMessageType() == 0x01;
538   }
539
540    /**
541     * @return true if the reply is for a CAN detector and the type is 0x01
542     */
543    public boolean isCanReporterMessage(){
544        int type = canDetectorMessageType();
545        return isCanDetectorMessage() && type >= 0x11 && type<= 0x1f;
546    }
547
548}