001package jmri.jmrix.roco.z21;
002
003import jmri.SpeedStepMode;
004import jmri.jmrix.lenz.XNetConstants;
005import jmri.jmrix.lenz.XNetMessage;
006import jmri.jmrix.lenz.XPressNetMessageFormatter;
007import org.reflections.Reflections;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011import java.lang.reflect.Constructor;
012import java.lang.reflect.InvocationTargetException;
013import java.util.ArrayList;
014import java.util.List;
015import java.util.Set;
016
017/**
018 * Represents a single command or response on the XpressNet.
019 * <p>
020 * Content is represented with ints to avoid the problems with sign-extension
021 * that bytes have, and because a Java char is actually a variable number of
022 * bytes in Unicode.
023 *
024 * @author Bob Jacobsen Copyright (C) 2002
025 * @author Paul Bender Copyright (C) 2003-2010
026 */
027public class Z21XNetMessage extends jmri.jmrix.lenz.XNetMessage {
028
029    /**
030     * Constructor, just pass on to the superclass.
031     * @param len message length.
032     */
033    public Z21XNetMessage(int len) {
034        super(len);
035    }
036
037    /**
038     * Constructor from a Z21Message.
039     * @param m the Z21Message.
040     */
041    public Z21XNetMessage(Z21Message m) {
042        super(m.getLength()-4);
043        for(int i = 4; i< m.getLength() ; i++ ){
044           this.setElement(i-4,m.getElement(i));
045        }
046    }
047
048    /**
049     * Create a new object, that is a copy of an existing message.
050     *
051     * @param message an existing Z21XpressNet message
052     */
053    public Z21XNetMessage(Z21XNetMessage message) {
054        super(message);
055    }
056
057    /**
058     * Create an Z21XNetMessage from an Z21XNetReply.
059     * @param message the existing Z21XNetReply.
060     */
061    public Z21XNetMessage(Z21XNetReply message) {
062        super(message);
063    }
064
065    /**
066     * Create an XNetMessage from a String containing bytes.
067     * @param s byte string.
068     */
069    public Z21XNetMessage(String s) {
070        super(s);
071    }
072    private static final List<XPressNetMessageFormatter> formatterList = new ArrayList<>();
073
074    @Override
075    public String toMonitorString() {
076
077        if(formatterList.isEmpty()) {
078            try {
079                Reflections reflections = new Reflections("jmri.jmrix");
080                Set<Class<? extends XPressNetMessageFormatter>> f = reflections.getSubTypesOf(XPressNetMessageFormatter.class);
081                for (Class<?> c : f) {
082                    log.debug("Found formatter: {}", f.getClass().getName());
083                    Constructor<?> ctor = c.getConstructor();
084                    formatterList.add((XPressNetMessageFormatter) ctor.newInstance());
085                }
086            } catch (NoSuchMethodException | SecurityException | InstantiationException |
087                     IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
088                log.error("Error instantiating formatter", e);
089            }
090        }
091
092        return formatterList.stream()
093                .filter(f -> f.handlesMessage(this))
094                .findFirst().map(f -> f.formatMessage(this))
095                .orElse(this.toString());
096    }
097
098    /**
099     * Create messages of a particular form.
100     * @param cv CV index
101     * @return message to send.
102     */
103    public static Z21XNetMessage getZ21ReadDirectCVMsg(int cv) {
104        Z21XNetMessage m = new Z21XNetMessage(5);
105        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
106        m.setTimeout(XNetProgrammingTimeout);
107        m.setElement(0, Z21Constants.LAN_X_CV_READ_XHEADER);
108        m.setElement(1, Z21Constants.LAN_X_CV_READ_DB0);
109        m.setElement(2, ((0xff00 & (cv - 1)) >> 8));
110        m.setElement(3, (0xff & (cv - 1)));
111        m.setParity(); // Set the parity bit
112        return m;
113    }
114
115    public static Z21XNetMessage getZ21WriteDirectCVMsg(int cv, int val) {
116        Z21XNetMessage m = new Z21XNetMessage(6);
117        m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE);
118        m.setTimeout(XNetProgrammingTimeout);
119        m.setElement(0, Z21Constants.LAN_X_CV_WRITE_XHEADER);
120        m.setElement(1, Z21Constants.LAN_X_CV_WRITE_DB0);
121        m.setElement(2, (0xff00 & (cv - 1)) >> 8);
122        m.setElement(3, (0xff & (cv - 1)));
123        m.setElement(4, val);
124        m.setParity(); // Set the parity bit
125        return m;
126    }
127
128    /**
129     * Given a locomotive address, request its status.
130     *
131     * @param address is the locomotive address
132     * @return message to send.
133     */
134    public static Z21XNetMessage getZ21LocomotiveInfoRequestMsg(int address) {
135        Z21XNetMessage msg = new Z21XNetMessage(5);
136        msg.setElement(0, XNetConstants.LOCO_STATUS_REQ);
137        msg.setElement(1, Z21Constants.LAN_X_LOCO_INFO_REQUEST_Z21);
138        msg.setElement(2, jmri.jmrix.lenz.LenzCommandStation.getDCCAddressHigh(address));
139        msg.setElement(3, jmri.jmrix.lenz.LenzCommandStation.getDCCAddressLow(address));
140        msg.setParity();
141        return (msg);
142    }
143
144    /**
145     * Given a locomotive address, a function number, and its value,
146     * generate a message to change the state.
147     *
148     * @param address is the locomotive address
149     * @param functionno is the function to change
150     * @param state is boolean representing whether the function is to be on or off
151     * @return message to send.
152     */
153    public static Z21XNetMessage getZ21LocomotiveFunctionOperationMsg(int address, int functionno, boolean state) {
154        Z21XNetMessage msg = new Z21XNetMessage(6);
155        int functionbyte = functionno;
156        msg.setElement(0, XNetConstants.LOCO_OPER_REQ);
157        msg.setElement(1, Z21Constants.LAN_X_SET_LOCO_FUNCTION);
158        msg.setElement(2, jmri.jmrix.lenz.LenzCommandStation.getDCCAddressHigh(address));
159        msg.setElement(3, jmri.jmrix.lenz.LenzCommandStation.getDCCAddressLow(address));
160        if(state) {
161           //This function is on
162           functionbyte = functionbyte & 0x3F; // clear the 2 most significant bits.
163           functionbyte = functionbyte | 0x40; // set the 2 msb to 01.
164        } else {
165           //This function is off.
166           functionbyte = functionbyte & 0x3F; // clear the 2 most significant bits.
167        }
168        msg.setElement(4, functionbyte);
169        msg.setParity();
170        msg.setBroadcastReply();
171        return (msg);
172    }
173
174    /**
175     * Generate a Z21 message to change the speed/direction of a locomotive.
176     *
177     * @param address the locomotive address
178     * @param speedMode the speedstep mode see @jmri.DccThrottle
179     *                       for possible values.
180     * @param speed a normalized speed value (a floating point number between 0
181     *              and 1).  A negative value indicates emergency stop.
182     * @param isForward true for forward, false for reverse.
183     * @return message to send.
184     */
185    public static XNetMessage getZ21LanXSetLocoDriveMsg(int address, SpeedStepMode speedMode, float speed, boolean isForward) {
186        XNetMessage msg = XNetMessage.getSpeedAndDirectionMsg(address,
187                        speedMode,speed,isForward);
188        msg.setBroadcastReply();
189        return (msg);
190    }
191
192
193    /**
194     * Given a turnout address, generate a message to request the state.
195     *
196     * @param address the turnout address
197     * @return message to send.
198     */
199    public static Z21XNetMessage getZ21TurnoutInfoRequestMessage(int address ) {
200        // refer to section 5.1 of the z21 lan protocol manual.
201        Z21XNetMessage msg = new Z21XNetMessage(4);
202        msg.setElement(0,Z21Constants.LAN_X_GET_TURNOUT_INFO);
203        // compared to Lenz devices, the addresses on the Z21 is one below 
204        // the numerical value.  We will correct it here so higher level 
205        // code doesn't see the difference.
206        msg.setElement(1,((address-1) &0xff00)>>8);
207        msg.setElement(2,((address-1) & 0x00ff));
208        msg.setParity();
209        return(msg);
210    }
211
212    /**
213     * Given a turnout address and whether or not it is thrown, generate 
214     * a message to operate the turnout.
215     *
216     * @param address is the turnout address
217     * @param thrown boolean value representing whether the turnout is thrown.
218     * @param active boolean value representing whether the output is being set
219     * to active.
220     * @param queue boolean value representing whehter or not the message is 
221     * added to the queue.
222     * @return message to send.
223     */
224    public static Z21XNetMessage getZ21SetTurnoutRequestMessage(int address, boolean thrown, boolean active, boolean queue) {
225        // refer to section 5.2 of the z21 lan protocol manual.
226        Z21XNetMessage msg = new Z21XNetMessage(5);
227        msg.setElement(0,Z21Constants.LAN_X_SET_TURNOUT);
228        // compared to Lenz devices, the addresses on the Z21 is one below 
229        // the numerical value.  We will correct it here so higher level 
230        // code doesn't see the difference.
231        msg.setElement(1,((address-1) &0xff00)>>8);
232        msg.setElement(2,((address-1) & 0x00ff));
233        int element3=0x80;
234        if(active) {
235           element3 |=  0x08;
236        } 
237        if(thrown) {
238           element3 |=  0x01;
239        } 
240        if(queue) {
241           element3 |=  0x20;
242        } 
243
244        msg.setElement(3,element3);
245        msg.setParity();
246        msg.setBroadcastReply();
247        return(msg);
248    }
249
250    private static final Logger log = LoggerFactory.getLogger(Z21XNetMessage.class);
251
252}