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