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