001package jmri.jmrix.marklin;
002
003/**
004 * Encodes a message to a Marklin command station.
005 * <p>
006 * The {@link MarklinReply} class handles the response from the command station.
007 * Packages of length 13 are interpreted as can-bus packages:
008 * 4 bytes Can-bus-ID (BigEndian or network order),
009 * 1-byte length and 
010 * 8 bytes of data, if necessary with null bytes to fill in.
011 * <p>
012 * The message ID is divided into the areas of lower priority (priority),
013 * command (command), response and hash.
014 * The communication is based on the following format:
015 * Prio - 2 +2bit
016 * Command 8 bit
017 * Resp - 1 bit
018 * Hash - 16bit
019 * DLC - 4bit (ie CAN message length)
020 * CAN message 8 BYTES
021 * Can Message Bytes 0 to 3 are the address bytes, with byte 0 High, byte 3 low
022 * @author Kevin Dickerson Copyright (C) 2001, 2008
023 */
024public class MarklinMessage extends jmri.jmrix.AbstractMRMessage {
025
026    static int MY_UID = 0x12345678;
027
028    MarklinMessage() {
029        _dataChars = new int[13];
030        _nDataChars = 13;
031        setBinary(true);
032        for (int i = 0; i < 13; i++) {
033            _dataChars[i] = 0x00;
034        }
035    }
036
037    // create a new one from an array
038    public MarklinMessage(int[] d) {
039        this();
040        System.arraycopy(d, 0, _dataChars, 0, d.length);
041    }
042
043    // create a new one from a byte array, as a service
044    public MarklinMessage(byte[] d) {
045        this();
046        for (int i = 0; i < d.length; i++) {
047            _dataChars[i] = d[i] & 0xFF;
048        }
049    }
050
051    // create a new one
052    public MarklinMessage(int i) {
053        this();
054    }
055
056    // copy one
057    public MarklinMessage(MarklinMessage m) {
058        super(m);
059    }
060
061    // static methods to return a formatted message
062    public static MarklinMessage getEnableMain() {
063        MarklinMessage m = new MarklinMessage();
064        m.setElement(0, MarklinConstants.SYSCOMMANDSTART & 0xFF);
065        m.setElement(1, 0x00 & 0xFF);
066        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
067        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
068        m.setElement(4, 0x05 & 0xFF); // DLC = 5 (five data bytes)
069        // Elements 5-8: Address bytes (0x00 for global command)
070        m.setElement(9, MarklinConstants.CMDGOSYS & 0xFF); // Data byte 0: Turn main on (0x01)
071        return m;
072    }
073
074    public static MarklinMessage getKillMain() {
075        MarklinMessage m = new MarklinMessage();
076        m.setElement(0, MarklinConstants.SYSCOMMANDSTART & 0xFF);
077        m.setElement(1, 0x00 & 0xFF);
078        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
079        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
080        m.setElement(4, 0x05 & 0xFF); // DLC = 5 (five data bytes)
081        // Elements 5-8: Address bytes (0x00 for global command)
082        m.setElement(9, MarklinConstants.CMDSTOPSYS & 0xFF); // Data byte 0: Turn main off (0x00)
083        return m;
084    }
085
086    /**
087     * Generate CAN BOOT command (0x1B) for Gleisbox activation.
088     * <p>
089     * This command resets the Gleisbox/trackbox and initiates it to start
090     * passing commands to locos and accessories on the rails. Without this
091     * command on startup, the hardware does not respond to subsequent commands.
092     * This variant is used for normal operational startup of standalone
093     * Gleisbox devices when no CS2/MS2 is attached.
094     * <p>
095     * The packet uses DLC=5 with data byte 0 set to 0x11, which is the
096     * "magic value" that activates the Gleisbox for normal operations.
097     * This matches the behavior of Rocrail and the can2udp reference
098     * implementation (M_GLEISBOX_MAGIC_START_SEQUENCE).
099     *
100     * @return MarklinMessage containing the CAN BOOT activation command
101     * @see <a href="https://github.com/GBert/railroad/can2udp">can2udp reference implementation</a>
102     * @see #getCanBootloaderMode()
103     */
104    public static MarklinMessage getCanBoot() {
105        MarklinMessage m = new MarklinMessage();
106        m.setElement(0, (0x1B >> 7) & 0xFF);  // Command 0x1B high bits (encodes to 0x00)
107        m.setElement(1, (0x1B << 1) & 0xFF);  // Command 0x1B low bits (encodes to 0x36)
108        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
109        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
110        m.setElement(4, 0x05 & 0xFF); // DLC = 5 (five data bytes)
111        // Elements 5-8: Address bytes (0x00 for broadcast)
112        m.setElement(9, 0x11 & 0xFF); // Data byte 0: Magic value 0x11 to activate Gleisbox
113        // Elements 10-12 remain as 0x00 (initialized in constructor)
114        return m;
115    }
116
117    /**
118     * Generate CAN BOOT command (0x1B) for entering bootloader mode.
119     * <p>
120     * This variant of the CAN BOOT command invokes the bootloader update
121     * sequence on Märklin devices for firmware updates. It is sent after
122     * a system reset with approximately 400ms wait time, putting the device
123     * into bootloader mode ready to receive firmware data.
124     * <p>
125     * This command uses DLC=0 (no data bytes) and should be followed by
126     * firmware data transfer packets if performing an actual firmware update.
127     * This is different from {@link #getCanBoot()} which activates the device
128     * for normal operations.
129     * <p>
130     * <strong>Note:</strong> This command is intended for firmware update
131     * operations. For normal Gleisbox activation to run trains, use
132     * {@link #getCanBoot()} instead.
133     *
134     * @return MarklinMessage containing the CAN BOOT bootloader invocation command
135     * @see <a href="https://www.stummiforum.de/t122854f7-M-rklin-CAN-Protokoll-x-B-commands-updates.html">Märklin CAN Protokoll 0x1B commands documentation</a>
136     * @see #getCanBoot()
137     */
138    public static MarklinMessage getCanBootloaderMode() {
139        MarklinMessage m = new MarklinMessage();
140        m.setElement(0, (0x1B >> 7) & 0xFF);  // Command 0x1B high bits (encodes to 0x00)
141        m.setElement(1, (0x1B << 1) & 0xFF);  // Command 0x1B low bits (encodes to 0x36)
142        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
143        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
144        m.setElement(4, 0x00 & 0xFF); // DLC = 0 (no data bytes - bootloader invocation)
145        // Elements 5-12 remain as 0x00 (initialized in constructor)
146        return m;
147    }
148
149    //static public MarklinMessage get
150    public static MarklinMessage getSetTurnout(int addr, int state, int power) {
151        MarklinMessage m = new MarklinMessage();
152        m.setElement(0, (MarklinConstants.ACCCOMMANDSTART >> 7) & 0xFF);
153        m.setElement(1, (MarklinConstants.ACCCOMMANDSTART << 1) & 0xFF);
154        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
155        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
156        m.setElement(4, 0x06 & 0xFF); // DLC = 6 (six data bytes)
157        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
158        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
159        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
160        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
161        m.setElement(9, state & 0xff);
162        m.setElement(10, power & 0xff);
163        return m;
164    }
165
166    public static MarklinMessage getQryLocoSpeed(int addr) {
167        MarklinMessage m = new MarklinMessage();
168        m.setElement(0, (MarklinConstants.LOCOSPEED >> 7) & 0xFF);
169        m.setElement(1, (MarklinConstants.LOCOSPEED << 1) & 0xFF);
170        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
171        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
172        m.setElement(4, 0x04 & 0xFF);
173        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
174        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
175        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
176        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
177        return m;
178    }
179
180    public static MarklinMessage setLocoSpeed(int addr, int speed) {
181        MarklinMessage m = new MarklinMessage();
182        m.setElement(0, (MarklinConstants.LOCOSPEED >> 7) & 0xFF);
183        m.setElement(1, (MarklinConstants.LOCOSPEED << 1) & 0xFF);
184        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
185        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
186        m.setElement(4, 0x06 & 0xFF);
187        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
188        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
189        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
190        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
191        m.setElement(9, (speed >> 8) & 0xff);
192        m.setElement(10, speed & 0xff);
193        return m;
194    }
195
196    public static MarklinMessage setLocoEmergencyStop(int addr) {
197        MarklinMessage m = new MarklinMessage();
198        m.setElement(0, MarklinConstants.SYSCOMMANDSTART & 0xFF);
199        m.setElement(1, 0x00 & 0xFF);
200        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
201        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
202        m.setElement(4, 0x05 & 0xFF); // DLC = 5 (five data bytes)
203        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
204        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
205        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
206        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
207        m.setElement(9, MarklinConstants.LOCOEMERGENCYSTOP & 0xFF);
208        return m;
209    }
210
211    public static MarklinMessage setLocoSpeedSteps(int addr, int step) {
212        MarklinMessage m = new MarklinMessage();
213        m.setElement(0, MarklinConstants.SYSCOMMANDSTART & 0xFF);
214        m.setElement(1, 0x00 & 0xFF);
215        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
216        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
217        m.setElement(4, 0x05 & 0xFF); // DLC = 5 (five data bytes)
218        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
219        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
220        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
221        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
222        m.setElement(9, 0x05 & 0xFF);
223        m.setElement(10, step & 0xFF);
224        return m;
225    }
226
227    public static MarklinMessage getQryLocoDirection(int addr) {
228        MarklinMessage m = new MarklinMessage();
229        m.setElement(0, (MarklinConstants.LOCODIRECTION >> 7) & 0xFF);
230        m.setElement(1, (MarklinConstants.LOCODIRECTION << 1) & 0xFF);
231        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
232        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
233        m.setElement(4, 0x04 & 0xFF);
234        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
235        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
236        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
237        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
238        return m;
239    }
240
241    public static MarklinMessage setLocoDirection(int addr, int dir) {
242        MarklinMessage m = new MarklinMessage();
243        m.setElement(0, (MarklinConstants.LOCODIRECTION >> 7) & 0xFF);
244        m.setElement(1, (MarklinConstants.LOCODIRECTION << 1) & 0xFF);
245        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
246        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
247        m.setElement(4, 0x05 & 0xFF);
248        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
249        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
250        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
251        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
252        m.setElement(9, dir & 0xff);
253        return m;
254    }
255
256    public static MarklinMessage getQryLocoFunction(int addr, int funct) {
257        MarklinMessage m = new MarklinMessage();
258        m.setElement(0, (MarklinConstants.LOCOFUNCTION >> 7) & 0xFF);
259        m.setElement(1, (MarklinConstants.LOCOFUNCTION << 1) & 0xFF);
260        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
261        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
262        m.setElement(4, 0x05 & 0xFF);
263        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
264        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
265        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
266        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
267        m.setElement(9, (funct) & 0xFF);
268        return m;
269    }
270
271    public static MarklinMessage setLocoFunction(int addr, int funct, int state) {
272        MarklinMessage m = new MarklinMessage();
273        m.setElement(0, (MarklinConstants.LOCOFUNCTION >> 7) & 0xFF);
274        m.setElement(1, (MarklinConstants.LOCOFUNCTION << 1) & 0xFF);
275        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
276        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
277        m.setElement(4, 0x06 & 0xFF);
278        m.setElement(MarklinConstants.CANADDRESSBYTE1, (addr >> 24) & 0xFF);
279        m.setElement(MarklinConstants.CANADDRESSBYTE2, (addr >> 16) & 0xFF);
280        m.setElement(MarklinConstants.CANADDRESSBYTE3, (addr >> 8) & 0xFF);
281        m.setElement(MarklinConstants.CANADDRESSBYTE4, (addr) & 0xFF);
282        m.setElement(9, funct & 0xff);
283        m.setElement(10, state & 0xff);
284        m.getAddress();
285        return m;
286    }
287
288    public static MarklinMessage sensorPollMessage(int module) {
289        MarklinMessage m = new MarklinMessage();
290        m.setElement(0, (MarklinConstants.FEECOMMANDSTART >> 7) & 0xFF);
291        m.setElement(1, (MarklinConstants.FEECOMMANDSTART << 1) & 0xFF);
292        m.setElement(2, MarklinConstants.HASHBYTE1 & 0xFF);
293        m.setElement(3, MarklinConstants.HASHBYTE2 & 0xFF);
294        m.setElement(4, 0x05 & 0xFF); // DLC = 5 (five data bytes)
295        m.setElement(MarklinConstants.CANADDRESSBYTE1, (MY_UID >> 24) & 0xFF);
296        m.setElement(MarklinConstants.CANADDRESSBYTE2, (MY_UID >> 16) & 0xFF);
297        m.setElement(MarklinConstants.CANADDRESSBYTE3, (MY_UID >> 8) & 0xFF);
298        m.setElement(MarklinConstants.CANADDRESSBYTE4, (MY_UID) & 0xFF);
299        m.setElement(9, module & 0xFF);
300        return m;
301    }
302
303    public long getAddress() {
304        long addr = getElement(MarklinConstants.CANADDRESSBYTE1);
305        addr = (addr << 8) + getElement(MarklinConstants.CANADDRESSBYTE2);
306        addr = (addr << 8) + getElement(MarklinConstants.CANADDRESSBYTE3);
307        addr = (addr << 8) + getElement(MarklinConstants.CANADDRESSBYTE4);
308
309        return addr;
310    }
311
312    public static MarklinMessage getProgMode() {
313        return null;
314    }
315
316    public static MarklinMessage getExitProgMode() {
317        return null;
318    }
319
320    public static MarklinMessage getReadPagedCV(int cv) { //Rxxx
321        return new MarklinMessage();
322    }
323
324    public static MarklinMessage getWritePagedCV(int cv, int val) { //Pxxx xxx
325        return new MarklinMessage();
326    }
327
328    public static MarklinMessage getReadRegister(int reg) { //Vx
329        return new MarklinMessage();
330    }
331
332    public static MarklinMessage getWriteRegister(int reg, int val) { //Sx xxx
333        return new MarklinMessage();
334    }
335
336    public static MarklinMessage getReadDirectCV(int cv) { //Rxxx
337        return new MarklinMessage();
338    }
339
340    public static MarklinMessage getWriteDirectCV(int cv, int val) { //Pxxx xxx
341        return new MarklinMessage();
342    }
343}