001package jmri.jmrix.roco.z21;
002
003import jmri.jmrix.AbstractMRMessage;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007/**
008 * Class for messages in the z21/Z21 protocol.
009 *
010 * Messages have the following format: 2 bytes data length. 2 bytes op code. n
011 * bytes data.
012 *
013 * All numeric values are stored in little endian format.
014 *
015 * Carries a sequence of characters, with accessors.
016 *
017 * @author Bob Jacobsen Copyright (C) 2003
018 * @author Paul Bender Copyright (C) 2014
019 */
020public class Z21Message extends AbstractMRMessage {
021
022    public Z21Message() {
023        super();
024        setBinary(true);
025    }
026
027    // create a new one
028    public Z21Message(int i) {
029        this();
030        if (i < 4) { // minimum length is 2 bytes of length, 2 bytes of opcode.
031            log.error("invalid length in call to ctor");
032        }
033        _nDataChars = i;
034        _dataChars = new int[i];
035        setLength(i);
036    }
037
038    // from an XpressNet message (used for protocol tunneling)
039    public Z21Message(jmri.jmrix.lenz.XNetMessage m) {
040        this(m.getNumDataElements() + 4);
041        this.setOpCode(0x0040);
042        for (int i = 0; i < m.getNumDataElements(); i++) {
043            setElement(i + 4, m.getElement(i));
044        }
045    }
046
047    // from an LocoNetNet message (used for protocol tunneling)
048    public Z21Message(jmri.jmrix.loconet.LocoNetMessage m) {
049        this(m.getNumDataElements() + 4);
050        if ((m.getOpCode() & 0x08) == 0x00) {
051            mReplyExpected = false;
052        }
053        this.setOpCode(0x00A2);
054        for (int i = 0; i < m.getNumDataElements(); i++) {
055            setElement(i + 4, m.getElement(i));
056        }
057    }
058
059    /**
060     * This ctor interprets the String as the exact sequence to send,
061     * byte-for-byte.
062     *
063     * @param m message string.
064     */
065    public Z21Message(String m) {
066        super(m);
067        setBinary(true);
068        // gather bytes in result
069        byte[] b = jmri.util.StringUtil.bytesFromHexString(m);
070        if (b.length == 0) {
071            // no such thing as a zero-length message
072            _nDataChars = 0;
073            _dataChars = null;
074            return;
075        }
076        _nDataChars = b.length;
077        _dataChars = new int[_nDataChars];
078        for (int i = 0; i < b.length; i++) {
079            setElement(i, b[i]);
080        }
081    }
082
083    /**
084     * This ctor interprets the byte array as a sequence of characters to send.
085     *
086     * @param a Array of bytes to send
087     * @param l unused.
088     */
089    public Z21Message(byte[] a, int l) {
090        super(String.valueOf(a));
091        setBinary(true);
092    }
093
094    boolean mReplyExpected = true;
095    @Override
096    public boolean replyExpected() {
097        return mReplyExpected; 
098    }
099
100    @Override
101    public void setOpCode(int i) {
102        _dataChars[2] = (i & 0x00ff);
103        _dataChars[3] = ((i & 0xff00) >> 8);
104    }
105
106    @Override
107    public int getOpCode() {
108        return ( (0xff & _dataChars[2]) + ((0xff & _dataChars[3]) << 8));
109    }
110
111    public void setLength(int i) {
112        _dataChars[0] = (i & 0x00ff);
113        _dataChars[1] = ((i & 0xff00) >> 8);
114    }
115
116    public int getLength() {
117        return (_dataChars[0] + (_dataChars[1] << 8));
118    }
119
120    /*
121     * package protected method to get the _dataChars buffer as bytes.
122     * @return byte array containing the low order bits of the  integer 
123     *         values in _dataChars.
124     */
125    byte[] getBuffer() {
126        byte[] byteData = new byte[_dataChars.length];
127        for (int i = 0; i < _dataChars.length; i++) {
128            byteData[i] = (byte) (0x00ff & _dataChars[i]);
129        }
130        return byteData;
131    }
132
133    /*
134     * canned messages
135     */
136
137    /*
138     * @return z21 message for serial number request.
139     */
140    public static Z21Message getSerialNumberRequestMessage() {
141        Z21Message retval = new Z21Message(4);
142        retval.setElement(0, 0x04);
143        retval.setElement(1, 0x00);
144        retval.setElement(2, 0x10);
145        retval.setElement(3, 0x00);
146        return retval;
147    }
148
149    /*
150     * @return z21 message for a hardware information request.
151     */
152    public static Z21Message getLanGetHardwareInfoRequestMessage() {
153        Z21Message retval = new Z21Message(4);
154        retval.setElement(0, 0x04);
155        retval.setElement(1, 0x00);
156        retval.setElement(2, 0x1A);
157        retval.setElement(3, 0x00);
158        return retval;
159    }
160
161    /*
162     * @return z21 message for LAN_LOGOFF request.
163     */
164    public static Z21Message getLanLogoffRequestMessage() {
165        Z21Message retval = new Z21Message(4){
166           @Override 
167           public boolean replyExpected() {
168               return false; // Loging off generates no reply.
169           }
170        };
171        retval.setElement(0, 0x04);
172        retval.setElement(1, 0x00);
173        retval.setElement(2, 0x30);
174        retval.setElement(3, 0x00);
175        return retval;
176    }
177
178    /**
179     * @return z21 message for LAN_GET_BROADCAST_FLAGS request.
180     */
181    public static Z21Message getLanGetBroadcastFlagsRequestMessage() {
182        Z21Message retval = new Z21Message(4);
183        retval.setElement(0, 0x04);
184        retval.setElement(1, 0x00);
185        retval.setElement(2, 0x51);
186        retval.setElement(3, 0x00);
187        return retval;
188    }
189
190    /**
191     * Set the broadcast flags as described in section 2.16 of the 
192     * Roco Z21 Protocol Manual.
193     * <p>
194     * Brief descriptions of the flags are as follows (losely 
195     * translated from German with the aid of google translate).
196     * <ul>
197     * <li>0x00000001 send XpressNet related information (track
198     * power on/off, programming mode, short circuit, broadcast stop, 
199     * locomotive information, turnout information).</li>
200     * <li>0x00000002 send data changes that occur on the RMBUS.</li>
201     * <li>0x00000004 (deprecated by Roco) send Railcom Data</li>
202     * <li>0x00000100 send changes in system state (such as track voltage)
203     * <li>0x00010000 send changes to locomotives on XpressNet (must also have
204     * 0x00000001 set.</li>
205     * <li>0x01000000 forward LocoNet data to the client.  Does not send
206     * Locomotive or turnout data.</li>
207     * <li>0x02000000 send Locomotive specific LocoNet data to the client.</li>
208     * <li>0x04000000 send Turnout specific LocoNet data to the client.</li>
209     * <li>0x08000000 send Occupancy information from LocoNet to the client</li>
210     * <li>0x00040000 Automatically send updates for Railcom data to the client</li>
211     * <li>0x00080000 send can detector messages to the client</li>
212     * </ul>
213     *
214     * @param flags integer representing the flags (32 bits).
215     * @return z21 message for LAN_SET_BROADCAST_FLAGS request.
216     */
217    public static Z21Message getLanSetBroadcastFlagsRequestMessage(int flags) {
218        Z21Message retval = new Z21Message(8){
219           @Override 
220           public boolean replyExpected() {
221               return false; // setting the broadcast flags generates 
222                             // no reply.
223           }
224        };
225        retval.setElement(0, 0x08);
226        retval.setElement(1, 0x00);
227        retval.setElement(2, 0x50);
228        retval.setElement(3, 0x00);
229        retval.setElement(4, (flags & 0x000000ff) );
230        retval.setElement(5, (flags & 0x0000ff00)>>8 );
231        retval.setElement(6, (flags & 0x00ff0000)>>16 );
232        retval.setElement(7, (flags & 0xff000000)>>24 );
233        return retval;
234    }
235
236
237    /**
238     * @return z21 message for LAN_RAILCOM_GETDATA request.
239     */
240    public static Z21Message getLanRailComGetDataRequestMessage() {
241        Z21Message retval = new Z21Message(4);
242        retval.setElement(0, 0x04);
243        retval.setElement(1, 0x00);
244        retval.setElement(2, 0x89);
245        retval.setElement(3, 0x00);
246        return retval;
247    }
248
249    /**
250     * @return z21 message for LAN_SYSTEMSTATE_GETDATA
251     */
252    public static Z21Message getLanSystemStateDataChangedRequestMessage(){
253        Z21Message retval = new Z21Message(4);
254        retval.setElement(0, 0x04);
255        retval.setElement(1, 0x00);
256        retval.setElement(2, 0x85);
257        retval.setElement(3, 0x00);
258        return retval;
259    }
260
261    @Override
262    public String toMonitorString() {
263        switch(getOpCode()){
264           case 0x0010:
265               return Bundle.getMessage("Z21MessageStringSerialNoRequest");
266           case 0x001A:
267               return Bundle.getMessage("Z21MessageStringVersionRequest");
268           case 0x0040:
269               return Bundle.getMessage("Z21MessageXpressNetTunnelRequest",new Z21XNetMessage(this).toMonitorString());
270           case 0x0050:
271               return Bundle.getMessage("Z21MessageSetBroadcastFlags",Z21MessageUtils.interpretBroadcastFlags(_dataChars));
272           case 0x0051:
273               return Bundle.getMessage("Z21MessageRequestBroadcastFlags");
274           case 0x00A2:
275               return Bundle.getMessage("Z21LocoNetLanMessage", getLocoNetMessage().toMonitorString());
276           case 0x0081:
277               return Bundle.getMessage("Z21RMBusGetDataRequest", getElement(4));
278           case 0x0082:
279               return Bundle.getMessage("Z21RMBusProgramModuleRequest", getElement(4));
280           case 0x0089:
281               return Bundle.getMessage("Z21_RAILCOM_GETDATA");
282           case 0x00C4:
283               int networkID = ( getElement(4) & 0xFF) + ((getElement(5) & 0xFF) << 8);
284               return Bundle.getMessage("Z21CANDetectorRequest",networkID);
285           default:
286        }
287        return toString();
288    }
289
290    // handle LocoNet messages tunneled in Z21 messages
291    boolean isLocoNetTunnelMessage() {
292        return( getOpCode() == 0x00A2);
293    }
294
295    boolean isLocoNetDispatchMessage() {
296       return (getOpCode() == 0x00A3);
297    }
298
299    boolean isLocoNetDetectorMessage() {
300       return (getOpCode() == 0x00A4);
301    }
302
303    jmri.jmrix.loconet.LocoNetMessage getLocoNetMessage() {
304        jmri.jmrix.loconet.LocoNetMessage lnr = null;
305        if (isLocoNetTunnelMessage()) {
306            int i = 4;
307            lnr = new jmri.jmrix.loconet.LocoNetMessage(getLength()-4);
308            for (; i < getLength(); i++) {
309                lnr.setElement(i - 4, getElement(i));
310            }
311        }
312        return lnr;
313    }
314
315    /**
316     * @param group the RM Bus group number to request.
317     * @return z21 message for LAN_RMBUS_GETDATA 
318     */
319    public static Z21Message getLanRMBusGetDataRequestMessage(int group){
320        if(group!=0 && group!=1){
321           throw new IllegalArgumentException("RMBus Group not 0 or 1");
322        }
323        Z21Message retval = new Z21Message(5);
324        retval.setElement(0, 0x04);
325        retval.setElement(1, 0x00);
326        retval.setElement(2, 0x81);
327        retval.setElement(3, 0x00);
328        retval.setElement(4, (group & 0xff));
329        return retval;
330    }
331
332    /**
333     * @param address the RM Bus address to write.
334     * @return z21 message for LAN_RMBUS_PROGRAMMODULE
335     */
336    public static Z21Message getLanRMBusProgramModuleMessage(int address){
337        if(address>20){
338           throw new IllegalArgumentException("RMBus Address > 20");
339        }
340        Z21Message retval = new Z21Message(5);
341        retval.setElement(0, 0x05);
342        retval.setElement(1, 0x00);
343        retval.setElement(2, 0x82);
344        retval.setElement(3, 0x00);
345        retval.setElement(4, (address & 0xff));
346        return retval;
347    }
348
349    // handle CAN Feedback/Railcom Messages
350    boolean isCanDetectorMessage() {
351        return (getOpCode() == 0x00C4);
352    }
353
354    /**
355     * @param address CAN NetworkID of the module to request data from.
356     * @return z21 message for LAN_CAN_DETECTOR request message
357     */
358    public static Z21Message getLanCanDetector(int address){
359        Z21Message retval = new Z21Message(7);
360        retval.setElement(0, 0x07);
361        retval.setElement(1, 0x00);
362        retval.setElement(2, 0xC4);
363        retval.setElement(3, 0x00);
364        retval.setElement(4, 0x00);// type, currently fixed.
365        retval.setElement(5, (address & 0xff));
366        retval.setElement(6, ((address & 0xff00)>>8));
367        return retval;
368    }
369
370    private static final Logger log = LoggerFactory.getLogger(Z21Message.class);
371
372}