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