001package jmri.jmrix.can.cbus;
002
003import jmri.ProgrammingMode;
004import jmri.jmrix.AbstractMessage;
005import jmri.jmrix.can.CanFrame;
006import jmri.jmrix.can.CanMessage;
007import jmri.jmrix.can.CanMutableFrame;
008import jmri.jmrix.can.CanReply;
009
010
011/**
012 * Class to allow use of CBUS concepts to access the underlying can message.
013 * <p>
014 * Methods that take a CanMessage or CanReply as argument:
015 * <ul>
016 * <li>CanMessage - Can Frame being sent by JMRI
017 * <li>CanReply - Can Frame being received by JMRI
018 * </ul>
019 * https://github.com/MERG-DEV/CBUSlib.
020 *
021 * @author Andrew Crosland Copyright (C) 2008
022 * @author Steve Young (C) 2018
023 */
024public class CbusMessage {
025
026    /**
027     * Return a CanReply for use in sensors, turnouts + light
028     * If a response event, set to normal event
029     * In future, this may also translate extended messages down to normal messages.
030     *
031     * @param original CanReply to be coverted to normal opc
032     * @return new CanReply perhaps converted from response OPC to normal OPC.
033     */
034    public static CanReply opcRangeToStl(CanReply original){
035        CanReply msg = new CanReply(original);
036        int opc = getOpcode(msg);
037        // log.debug(" about to check opc {} ",opc);
038        switch (opc) {
039            case CbusConstants.CBUS_ARON:
040                msg.setElement(0, CbusConstants.CBUS_ACON);
041                break;
042            case CbusConstants.CBUS_AROF:
043                msg.setElement(0, CbusConstants.CBUS_ACOF);
044                break;
045            case CbusConstants.CBUS_ARSON:
046                msg.setElement(0, CbusConstants.CBUS_ASON);
047                break;
048            case CbusConstants.CBUS_ARSOF:
049                msg.setElement(0, CbusConstants.CBUS_ASOF);
050                break;
051            default:
052                break;
053        }
054        return msg;
055    }
056
057
058    /**
059     * Get the Op Code from the CanMessage
060     *
061     * @param am CanMessage or CanReply
062     * @return OPC of the message
063     */
064    public static int getOpcode(AbstractMessage am) {
065        return  am.getElement(0);
066    }
067
068    /**
069     * Get the Data Length from the CanMessage
070     *
071     * @param am CanMessage or CanReply
072     * @return the message data length
073     */
074    public static int getDataLength(AbstractMessage am) {
075        return am.getElement(0) >> 5;
076    }
077
078    /**
079     * Get the Node Number from a CanFrame Event
080     *
081     * @param am CanMessage or CanReply
082     * @return the node number if not a short event
083     */
084    public static int getNodeNumber(AbstractMessage am) {
085        if (isEvent(am) && !isShort(am) ) {
086            return am.getElement(1) * 256 + am.getElement(2);
087        } else {
088            return 0;
089        }
090    }
091
092    /**
093     * Get the Event Number from a CBUS Event
094     *
095     * @param m CanMessage or CanReply
096     * @return the message event ( device ) number, else -1 if not an event.
097     */
098    public static int getEvent(AbstractMessage m) {
099        if (isEvent(m)) {
100            return m.getElement(3) * 256 + m.getElement(4);
101        } else {
102            return -1;
103        }
104    }
105
106    /**
107     * Get the Event Type ( on or off ) from a CanFrame
108     *
109     * @param am CanFrame or CanReply
110     * @return CbusConstant EVENT_ON or EVENT_OFF
111     */
112    public static int getEventType(AbstractMessage am) {
113        if ( CbusOpCodes.isOnEvent(am.getElement(0))) {
114            return CbusConstants.EVENT_ON;
115        } else {
116            return CbusConstants.EVENT_OFF;
117        }
118    }
119
120    /**
121     * Tests if a CanMessage or CanReply is an Event.
122     * Performs Extended and RTR check.
123     * Adheres to cbus spec, ie on off responses to an AREQ are events.
124     *
125     * @param am CanMessage or CanReply
126     * @return True if event, else False.
127     */
128    public static boolean isEvent(AbstractMessage am) {
129        if ( am instanceof CanFrame && ((CanFrame)am).extendedOrRtr()){
130            return false;
131        }
132        return CbusOpCodes.isEvent(am.getElement(0));
133    }
134
135    /**
136     * Tests if CanFrame is a short event
137     *
138     * @param am CanReply or CanMessage
139     * @return true if Short Event, else false
140     */
141    public static boolean isShort(AbstractMessage am) {
142        return CbusOpCodes.isShortEvent(am.getElement(0));
143    }
144
145    /**
146     * Set the CAN ID within a CanMessage or CanReply Header
147     *
148     * @param am CanMessage or CanReply
149     * @param id CAN ID
150     */
151    public static void setId(AbstractMessage am, int id) throws IllegalArgumentException {
152        if (am instanceof CanMutableFrame){
153            CanMutableFrame m = (CanMutableFrame) am;
154            int update = m.getHeader();
155            if (m.isExtended()) {
156                throw new IllegalArgumentException("No CAN ID Concept on Extended CBUS CAN Frame.");
157            } else {
158                if ((id & ~0x7f) != 0) {
159                    throw new IllegalArgumentException("invalid standard ID value: " + id);
160                }
161                m.setHeader((update & ~0x07f) | id);
162            }
163        }
164        else {
165            throw new IllegalArgumentException(am + " is Not a CanMutableFrame");
166        }
167    }
168
169    /**
170     * Set the priority within a CanMessage or CanReply Header.
171     *
172     * @param am CanMessage or CanReply
173     * @param pri Priority
174     */
175    public static void setPri(AbstractMessage am, int pri) throws IllegalArgumentException {
176        if (am instanceof CanMutableFrame){
177            CanMutableFrame m = (CanMutableFrame) am;
178            if ((pri & ~0x0F) != 0) {
179                throw new IllegalArgumentException("Invalid CBUS Priority value: " + pri);
180            }
181            if (m.isExtended()) {
182                throw new IllegalArgumentException("Extended CBUS CAN Frames do not have a priority concept.");
183            } else {
184                m.setHeader((m.getHeader() & ~0x780) | (pri << 7));
185            }
186        }
187        else {
188            throw new IllegalArgumentException(am + " is Not a CanMutableFrame");
189        }
190    }
191
192    /**
193     * Returns string form of a CanMessage ( a Can Frame sent by JMRI )
194     * Short / Long events converted to Sensor / Turnout / Light hardware address
195     * message priority not indicated
196     * @param  m CanReply or CanMessage
197     * @return String of hardware address form
198     */
199    public static String toAddress(AbstractMessage m) {
200        switch (m.getElement(0)) {
201            case CbusConstants.CBUS_ACON:
202                // + form
203                return "+n" + (m.getElement(1) * 256 + m.getElement(2)) + "e" + (m.getElement(3) * 256 + m.getElement(4));
204            case CbusConstants.CBUS_ACOF:
205                // - form
206                return "-n" + (m.getElement(1) * 256 + m.getElement(2)) + "e" + (m.getElement(3) * 256 + m.getElement(4));
207            case CbusConstants.CBUS_ASON:
208                // + short form
209                return "+" + (m.getElement(3) * 256 + m.getElement(4));
210            case CbusConstants.CBUS_ASOF:
211                // - short form
212                return "-" + (m.getElement(3) * 256 + m.getElement(4));
213            default:
214                // hex form
215                String tmp = m.toString().replaceAll("\\s*\\[[^\\]]*\\]\\s*", ""); // remove the [header]
216                return "X" + tmp.replaceAll(" ", "");
217        }
218    }
219
220    /**
221     * Checks if a CanMessage is requesting Track Power Off
222     *
223     * @param  m Can Frame Message
224     * @return boolean
225     */
226    public static boolean isRequestTrackOff(CanMessage m) {
227        return m.getOpCode() == CbusConstants.CBUS_RTOF;
228    }
229
230    /**
231     * Checks if a CanMessage is requesting Track Power On
232     *
233     * @param  m Can Frame Message
234     * @return True if outgoing track power on request
235     */
236    public static boolean isRequestTrackOn(CanMessage m) {
237        return m.getOpCode() == CbusConstants.CBUS_RTON;
238    }
239
240    /**
241     * Get the CAN ID within a CanReply or CanMessage Header
242     *
243     * @param f CanReply or CanMessage
244     * @return CAN ID of the outgoing message
245     */
246    public static int getId(AbstractMessage f) throws IllegalArgumentException {
247        if (f instanceof CanFrame){
248            CanFrame cfMsg = (CanFrame) f;
249            if (cfMsg.isExtended()) {
250                return cfMsg.getHeader() & 0x1FFFFF;
251            } else {
252                return cfMsg.getHeader() & 0x7f;
253            }
254        }
255        else {
256            throw new IllegalArgumentException(f + " is Not a CanFrame");
257        }
258    }
259
260    /**
261     * Get the priority from within the CanReply or CanMessage Header
262     *
263     * @param r CanReply or CanMessage
264     * @return Priority of the outgoing message
265     */
266    public static int getPri(AbstractMessage r) throws IllegalArgumentException {
267        if (r instanceof CanFrame){
268            CanFrame cfMsg = (CanFrame) r;
269            if (cfMsg.isExtended()) {
270                return (cfMsg.getHeader() >> 25) & 0x0F;
271            } else {
272                return (cfMsg.getHeader() >> 7) & 0x0F;
273            }
274        }
275        else {
276            throw new IllegalArgumentException(r + " is Not a CanFrame");
277        }
278    }
279
280    /**
281     * Tests if CanReply is confirming Track Power Off.
282     *
283     * @param m CanReply
284     * @return True if is a Track Off notification
285     */
286    public static boolean isTrackOff(CanReply m) {
287        return m.getOpCode() == CbusConstants.CBUS_TOF;
288    }
289
290    /**
291     * Tests if CanReply is confirming Track Power On.
292     *
293     * @param m CanReply
294     * @return True if is a Track On notification
295     */
296    public static boolean isTrackOn(CanReply m) {
297        return m.getOpCode() == CbusConstants.CBUS_TON;
298    }
299
300    /**
301     * Tests if CanReply is a System Reset
302     *
303     * @param m CanReply
304     * @return True if emergency Stop
305     */
306    public static boolean isArst(CanReply m) {
307        return m.getOpCode() == CbusConstants.CBUS_ARST;
308    }
309
310    /**
311     * CBUS programmer commands
312     * @param cv CV to read
313     * @param mode Programming Mode
314     * @param header CAN ID
315     * @return CanMessage ready to send
316     */
317    static public CanMessage getReadCV(int cv, ProgrammingMode mode, int header) {
318        CanMessage m = new CanMessage(5, header);
319        m.setElement(0, CbusConstants.CBUS_QCVS);
320        m.setElement(1, CbusConstants.SERVICE_HANDLE);
321        m.setElement(2, (cv / 256) & 0xff);
322        m.setElement(3, cv & 0xff);
323        if (mode.equals(ProgrammingMode.PAGEMODE)) {
324            m.setElement(4, CbusConstants.CBUS_PROG_PAGED);
325        } else if (mode.equals(ProgrammingMode.DIRECTBITMODE)) {
326            m.setElement(4, CbusConstants.CBUS_PROG_DIRECT_BIT);
327        } else if (mode.equals(ProgrammingMode.DIRECTBYTEMODE)) {
328            m.setElement(4, CbusConstants.CBUS_PROG_DIRECT_BYTE);
329        } else {
330            m.setElement(4, CbusConstants.CBUS_PROG_REGISTER);
331        }
332        setPri(m, 0xb);
333        return m;
334    }
335
336    /**
337     * CBUS programmer commands
338     *
339     * CBUS VCVS works like a QCVS read but the programmer will first check if
340     * the CV contents are equal to the startVal. This can speed up CV reads by
341     * skipping reading of other values.
342     *
343     * @param cv CV to read
344     * @param mode Programming Mode
345     * @param startVal Hint of current CV value
346     * @param header CAN ID
347     * @return CanMessage ready to send
348     */
349    static public CanMessage getVerifyCV(int cv, ProgrammingMode mode, int startVal, int header) {
350        CanMessage m = new CanMessage(6, header);
351        m.setElement(0, CbusConstants.CBUS_VCVS);
352        m.setElement(1, CbusConstants.SERVICE_HANDLE);
353        m.setElement(2, (cv / 256) & 0xff);
354        m.setElement(3, cv & 0xff);
355        if (mode.equals(ProgrammingMode.PAGEMODE)) {
356            m.setElement(4, CbusConstants.CBUS_PROG_PAGED);
357        } else if (mode.equals(ProgrammingMode.DIRECTBITMODE)) {
358            m.setElement(4, CbusConstants.CBUS_PROG_DIRECT_BIT);
359        } else if (mode.equals(ProgrammingMode.DIRECTBYTEMODE)) {
360            m.setElement(4, CbusConstants.CBUS_PROG_DIRECT_BYTE);
361        } else {
362            m.setElement(4, CbusConstants.CBUS_PROG_REGISTER);
363        }
364        m.setElement(5, startVal & 0xff);
365         setPri(m, 0xb);
366        return m;
367    }
368
369    /**
370     * Get a CanMessage to write a CV.
371     * @param cv Which CV, 0-65534
372     * @param val New CV value, 0-255
373     * @param mode Programming Mode
374     * @param header CAN ID
375     * @return ready to send CanMessage
376     */
377    static public CanMessage getWriteCV(int cv, int val, ProgrammingMode mode, int header) {
378        CanMessage m = new CanMessage(6, header);
379        m.setElement(0, CbusConstants.CBUS_WCVS);
380        m.setElement(1, CbusConstants.SERVICE_HANDLE);
381        m.setElement(2, (cv / 256) & 0xff);
382        m.setElement(3, cv & 0xff);
383        if (mode.equals(ProgrammingMode.PAGEMODE)) {
384            m.setElement(4, CbusConstants.CBUS_PROG_PAGED);
385        } else if (mode.equals(ProgrammingMode.DIRECTBITMODE)) {
386            m.setElement(4, CbusConstants.CBUS_PROG_DIRECT_BIT);
387        } else if (mode.equals(ProgrammingMode.DIRECTBYTEMODE)) {
388            m.setElement(4, CbusConstants.CBUS_PROG_DIRECT_BYTE);
389        } else {
390            m.setElement(4, CbusConstants.CBUS_PROG_REGISTER);
391        }
392        m.setElement(5, val);
393        setPri(m, 0xb);
394        return m;
395    }
396
397    /**
398     * CBUS Ops mode programmer commands
399     * @param mAddress Loco Address, non-DCC format
400     * @param mLongAddr If Loco Address is a long address
401     * @param header CAN ID
402     * @param val New CV value
403     * @param cv Which CV, 0-65534
404     * @return ready to send CanMessage
405     */
406    static public CanMessage getOpsModeWriteCV(int mAddress, boolean mLongAddr, int cv, int val, int header) {
407        CanMessage m = new CanMessage(7, header);
408        int address = mAddress;
409        m.setElement(0, CbusConstants.CBUS_WCVOA);
410        if (mLongAddr) {
411            address = address | 0xc000;
412        }
413        m.setElement(1, address / 256);
414        m.setElement(2, address & 0xff);
415        m.setElement(3, (cv / 256) & 0xff);
416        m.setElement(4, cv & 0xff);
417        m.setElement(5, CbusConstants.CBUS_OPS_BYTE);
418        m.setElement(6, val);
419        setPri(m, 0xb);
420        return m;
421    }
422
423    /**
424     * Get a CanMessage to send track power on
425     *
426     * @param header for connection CAN ID
427     * @return the CanMessage to send to request track power on
428     */
429    static public CanMessage getRequestTrackOn(int header) {
430        CanMessage m = new CanMessage(1, header);
431        m.setElement(0, CbusConstants.CBUS_RTON);
432        setPri(m, 0xb);
433        return m;
434    }
435
436    /**
437     * Get a CanMessage to send track power off
438     *
439     * @param header for connection CAN ID
440     * @return the CanMessage to send to request track power off
441     */
442    static public CanMessage getRequestTrackOff(int header) {
443        CanMessage m = new CanMessage(1, header);
444        m.setElement(0, CbusConstants.CBUS_RTOF);
445        setPri(m, 0xb);
446        return m;
447    }
448
449
450    // CBUS bootloader commands
451
452    /**
453     * This is a strict CBUS message to put a node into boot mode.
454     * @param nn Node Number 1-65534
455     * @param header CAN ID
456     * @return ready to send CanMessage
457     */
458    static public CanMessage getBootEntry(int nn, int header) {
459        CanMessage m = new CanMessage(3, header);
460        m.setElement(0, CbusConstants.CBUS_BOOTM);
461        m.setElement(1, (nn / 256) & 0xFF);
462        m.setElement(2, nn & 0xFF);
463        setPri(m, 0xb);
464        return m;
465    }
466
467    /**
468     * Microchip AN247 format NOP message to set address.
469     * <p>
470     * The CBUS bootloader uses extended ID frames
471     *
472     * @param a address
473     * @param header CAN ID - overridden by call to setHeader
474     * @return ready to send CanMessage
475     */
476    static public CanMessage getBootNop(int a, int header) {
477        CanMessage m = new CanMessage(8, header);
478        m.setExtended(true);
479        m.setHeader(0x4);
480        m.setElement(0, a & 0xFF);
481        m.setElement(1, (a / 256) & 0xFF);
482        m.setElement(2, (a / 65536) & 0xFF);
483        m.setElement(3, 0);
484        //m.setElement(4, 0x0D);
485        m.setElement(4, CbusConstants.CBUS_BOOT_MODE_ACK
486                      | CbusConstants.CBUS_BOOT_MODE_AUTO_INC
487                      | CbusConstants.CBUS_BOOT_MODE_AUTO_ERASE
488                      | CbusConstants.CBUS_BOOT_MODE_WRT_UNLCK
489                    );
490        m.setElement(5, CbusConstants.CBUS_BOOT_NOP);
491        m.setElement(6, 0);
492        m.setElement(7, 0);
493        return m;
494    }
495
496    /**
497     * Microchip AN247 format message to reset and enter normal mode.
498     *
499     * @param header CAN ID - overridden by call to setHeader
500     * @return ready to send CanMessage
501     */
502    static public CanMessage getBootReset(int header) {
503        CanMessage m = new CanMessage(8, header);
504        m.setExtended(true);
505        m.setHeader(0x4);
506        m.setElement(0, 0);
507        m.setElement(1, 0);
508        m.setElement(2, 0);
509        m.setElement(3, 0);
510        //m.setElement(4, 0x0D);
511        m.setElement(4, CbusConstants.CBUS_BOOT_MODE_ACK
512                      | CbusConstants.CBUS_BOOT_MODE_AUTO_INC
513                      | CbusConstants.CBUS_BOOT_MODE_AUTO_ERASE
514                      | CbusConstants.CBUS_BOOT_MODE_WRT_UNLCK
515                    );
516        m.setElement(5, CbusConstants.CBUS_BOOT_RESET);
517        m.setElement(6, 0);
518        m.setElement(7, 0);
519        return m;
520    }
521
522    /**
523     * Microchip AN247 format message to initialise the bootloader and set the
524     * start address.
525     *
526     * @param a start address
527     * @param header CAN ID - overridden by call to setHeader
528     * @return ready to send CanMessage
529     */
530    static public CanMessage getBootInitialise(int a, int header) {
531        CanMessage m = new CanMessage(8, header);
532        m.setExtended(true);
533        m.setHeader(0x4);
534        m.setElement(0, a & 0xFF);
535        m.setElement(1, (a / 256) & 0xFF);
536        m.setElement(2, (a / 65536) & 0xFF);
537        m.setElement(3, 0);
538        //m.setElement(4, 0x0D);
539        m.setElement(4, CbusConstants.CBUS_BOOT_MODE_ACK
540                      | CbusConstants.CBUS_BOOT_MODE_AUTO_INC
541                      | CbusConstants.CBUS_BOOT_MODE_AUTO_ERASE
542                      | CbusConstants.CBUS_BOOT_MODE_WRT_UNLCK
543                    );
544        m.setElement(5, CbusConstants.CBUS_BOOT_INIT);
545        m.setElement(6, 0);
546        m.setElement(7, 0);
547        return m;
548    }
549
550    /**
551     * Microchip AN247 format message to send the checksum for comparison.
552     *
553     * At time of writing [6th Feb '20] The MERG bootloader doc is incorrect and
554     * shows the checksum as being byte swapped.
555     *
556     * @param c 0-65535 2's complement of sum of all program bytes sent
557     * @param header CAN ID - overridden by call to setHeader
558     * @return ready to send CanMessage
559     */
560    static public CanMessage getBootCheck(int c, int header) {
561        CanMessage m = new CanMessage(8, header);
562        m.setExtended(true);
563        m.setHeader(0x4);
564        m.setElement(0, 0);
565        m.setElement(1, 0);
566        m.setElement(2, 0);
567        m.setElement(3, 0);
568        //m.setElement(4, 0x0D);
569        m.setElement(4, CbusConstants.CBUS_BOOT_MODE_ACK
570                      | CbusConstants.CBUS_BOOT_MODE_AUTO_INC
571                      | CbusConstants.CBUS_BOOT_MODE_AUTO_ERASE
572                      | CbusConstants.CBUS_BOOT_MODE_WRT_UNLCK
573                    );
574        m.setElement(5, CbusConstants.CBUS_BOOT_CHECK);
575        m.setElement(6, c & 0xff);
576        m.setElement(7, (c >> 8) & 0xff);
577        return m;
578    }
579
580    /**
581     * Microchip AN247 format message to check if a module is in boot mode.
582     *
583     * @param header CAN ID - overridden by call to setHeader
584     * @return ready to send CanMessage
585     */
586    static public CanMessage getBootTest(int header) {
587        CanMessage m = new CanMessage(8, header);
588        m.setExtended(true);
589        m.setHeader(0x4);
590        m.setElement(0, 0);
591        m.setElement(1, 0);
592        m.setElement(2, 0);
593        m.setElement(3, 0);
594        //m.setElement(4, 0x0D);
595        m.setElement(4, CbusConstants.CBUS_BOOT_MODE_ACK
596                      | CbusConstants.CBUS_BOOT_MODE_AUTO_INC
597                      | CbusConstants.CBUS_BOOT_MODE_AUTO_ERASE
598                      | CbusConstants.CBUS_BOOT_MODE_WRT_UNLCK
599                    );
600        m.setElement(5, CbusConstants.CBUS_BOOT_TEST);
601        m.setElement(6, 0);
602        m.setElement(7, 0);
603        return m;
604    }
605
606    /**
607     * CBUS bootloader v1.0 format message to request device ID.
608     *
609     * @param header CAN ID - overridden by call to setHeader
610     * @return ready to send CanMessage
611     */
612    static public CanMessage getBootDevId(int header) {
613        CanMessage m = new CanMessage(8, header);
614        m.setExtended(true);
615        m.setHeader(0x4);
616        m.setElement(0, 0);
617        m.setElement(1, 0);
618        m.setElement(2, 0);
619        m.setElement(3, 0);
620        //m.setElement(4, 0x0D);
621        m.setElement(4, CbusConstants.CBUS_BOOT_MODE_ACK
622                      | CbusConstants.CBUS_BOOT_MODE_AUTO_INC
623                      | CbusConstants.CBUS_BOOT_MODE_AUTO_ERASE
624                      | CbusConstants.CBUS_BOOT_MODE_WRT_UNLCK
625                    );
626        m.setElement(5, CbusConstants.CBUS_BOOT_DEVID);
627        m.setElement(6, 0);
628        m.setElement(7, 0);
629        return m;
630    }
631
632    /**
633     * CBUS bootloader v1.0 format message to request bootloader ID.
634     *
635     * @param header CAN ID - overridden by call to setHeader
636     * @return ready to send CanMessage
637     */
638    static public CanMessage getBootId(int header) {
639        CanMessage m = new CanMessage(8, header);
640        m.setExtended(true);
641        m.setHeader(0x4);
642        m.setElement(0, 0);
643        m.setElement(1, 0);
644        m.setElement(2, 0);
645        m.setElement(3, 0);
646        //m.setElement(4, 0x0D);
647        m.setElement(4, CbusConstants.CBUS_BOOT_MODE_ACK
648                      | CbusConstants.CBUS_BOOT_MODE_AUTO_INC
649                      | CbusConstants.CBUS_BOOT_MODE_AUTO_ERASE
650                      | CbusConstants.CBUS_BOOT_MODE_WRT_UNLCK
651                    );
652        m.setElement(5, CbusConstants.CBUS_BOOT_BOOTID);
653        m.setElement(6, 0);
654        m.setElement(7, 0);
655        return m;
656    }
657
658    /**
659     * CBUS bootloader v1.0 format message to set memory region write enables
660     *
661     * @param enables enable bits for memory regions
662     * @param header CAN ID - overridden by call to setHeader
663     * @return ready to send CanMessage
664     */
665    static public CanMessage getBootEnables(int enables, int header) {
666        CanMessage m = new CanMessage(8, header);
667        m.setExtended(true);
668        m.setHeader(0x4);
669        m.setElement(0, 0);
670        m.setElement(1, 0);
671        m.setElement(2, 0);
672        m.setElement(3, 0);
673        //m.setElement(4, 0x0D);
674        m.setElement(4, CbusConstants.CBUS_BOOT_MODE_ACK
675                      | CbusConstants.CBUS_BOOT_MODE_AUTO_INC
676                      | CbusConstants.CBUS_BOOT_MODE_AUTO_ERASE
677                      | CbusConstants.CBUS_BOOT_MODE_WRT_UNLCK
678                    );
679        m.setElement(5, CbusConstants.CBUS_BOOT_ENABLES);
680        m.setElement(6, enables & 0xFF);
681        m.setElement(7, 0);
682        return m;
683    }
684
685    /**
686     * Microchip AN247 format message to write 8 bytes of data
687     *
688     * @param d data array, 8 length, values 0-255
689     * @param header CAN ID - overridden by call to setHeader
690     * @return ready to send CanMessage
691     */
692    static public CanMessage getBootWriteData(int[] d, int header) {
693        CanMessage m = new CanMessage(d.length, header);
694        m.setExtended(true);
695        m.setHeader(0x5);
696        for (int i = 0; i < d.length; i++) {
697            m.setElement(i, d[i] & 0xff);
698        }
699        return m;
700    }
701
702    /**
703     * Microchip AN247 format message to write up to 8 bytes of data
704     *
705     * @param d data array, values 0-255
706     * @param header CAN ID - overridden by call to setHeader
707     * @return ready to send CanMessage
708     */
709    static public CanMessage getBootWriteData(byte[] d, int header) {
710        CanMessage m = new CanMessage(d.length, header);
711        m.setExtended(true);
712        m.setHeader(0x5);
713        for (int i = 0; i < d.length; i++) {
714            m.setElement(i, d[i] & 0xff);
715        }
716        return m;
717    }
718
719    /**
720     * Tests if a message is a bootloader data write
721     *
722     * @param m message
723     * @return true if the message is a bootloader data write
724     */
725    public static boolean isBootWriteData(CanMessage m) {
726        if (m.isExtended() && (m.getHeader() == 0x5)) {
727            return (true);
728        }
729        return (false);
730    }
731
732    /**
733     * Tests if incoming CanReply is a Boot Command Error.
734     *
735     * @param r CanReply
736     * @return True if is a Boot Command Error
737     */
738    public static boolean isBootError(CanReply r) {
739        if (r.isExtended() && (r.getHeader() == 0x10000004) && (r.getElement(0) == CbusConstants.CBUS_EXT_BOOT_ERROR)
740                && (r.getNumDataElements() == 1)) {
741            return (true);
742        }
743        return (false);
744    }
745
746    /**
747     * Tests if incoming CanReply is a Boot Data Error.
748     *
749     * @param r CanReply
750     * @return True if is a Boot Data Error
751     */
752    public static boolean isBootDataError(CanReply r) {
753        if (r.isExtended() && (r.getHeader() == 0x10000005) && (r.getElement(0) == CbusConstants.CBUS_EXT_BOOT_ERROR)
754                && (r.getNumDataElements() == 1)) {
755            return (true);
756        }
757        return (false);
758    }
759
760    /**
761     * Tests if incoming CanReply is a Boot Command OK.
762     *
763     * @param r CanReply
764     * @return True if is a Boot COmmand OK
765     */
766    public static boolean isBootOK(CanReply r) {
767        if (r.isExtended() && (r.getHeader() == 0x10000004) && (r.getElement(0) == CbusConstants.CBUS_EXT_BOOT_OK)
768                && (r.getNumDataElements() == 1)) {
769            return (true);
770        }
771        return (false);
772    }
773
774    /**
775     * Tests if incoming CanReply is a Boot Data OK.
776     *
777     * @param r CanReply
778     * @return True if is a Boot Data OK
779     */
780    public static boolean isBootDataOK(CanReply r) {
781        if (r.isExtended() && (r.getHeader() == 0x10000005) && (r.getElement(0) == CbusConstants.CBUS_EXT_BOOT_OK)
782                && (r.getNumDataElements() == 1)) {
783            return (true);
784        }
785        return (false);
786    }
787
788    /**
789     * Tests if incoming CanReply is a Boot Out of Range
790     *
791     * @param r CanReply
792     * @return True if is a Boot Data OK
793     */
794    public static boolean isBootOutOfRange(CanReply r) {
795        if (r.isExtended() && (r.getHeader() == 0x10000004) && (r.getElement(0) == CbusConstants.CBUS_EXT_BOOT_OUT_OF_RANGE)
796                && (r.getNumDataElements() == 1)) {
797            return (true);
798        }
799        return (false);
800    }
801
802    /**
803     * Tests if incoming CanReply is a Boot Out of Range
804     *
805     * @param r CanReply
806     * @return True if is a Boot Data OK
807     */
808    public static boolean isBootDataOutOfRange(CanReply r) {
809        if (r.isExtended() && (r.getHeader() == 0x10000005) && (r.getElement(0) == CbusConstants.CBUS_EXT_BOOT_OUT_OF_RANGE)
810                && (r.getNumDataElements() == 1)) {
811            return (true);
812        }
813        return (false);
814    }
815
816    /**
817     * Tests if incoming CanReply is a Boot Confirm.
818     *
819     * @param r CanReply
820     * @return True if is a Boot Confirm
821     */
822    public static boolean isBootConfirm(CanReply r) {
823        if (r.isExtended() && (r.getHeader() == 0x10000004) && (r.getElement(0) == CbusConstants.CBUS_EXT_BOOTC)
824                && (r.getNumDataElements() == 1)) {
825            return (true);
826        }
827        return (false);
828    }
829
830    /**
831     * Tests if incoming CanReply is a device ID reply.
832     *
833     * @param r CanReply
834     * @return True if is a Boot Confirm
835     */
836    public static boolean isBootDevId(CanReply r) {
837        if (r.isExtended() && (r.getHeader() == 0x10000004) && (r.getElement(0) == CbusConstants.CBUS_EXT_DEVID)
838                && (r.getNumDataElements() == 7)) {
839            return (true);
840        }
841        return (false);
842    }
843
844    /**
845     * Tests if incoming CanReply is a bootloader ID reply.
846     *
847     * @param r CanReply
848     * @return True if is a Boot Confirm
849     */
850    public static boolean isBootId(CanReply r) {
851        if (r.isExtended() && (r.getHeader() == 0x10000004) && (r.getElement(0) == CbusConstants.CBUS_EXT_BOOTID)
852                && (r.getNumDataElements() == 5)) {
853            return (true);
854        }
855        return (false);
856    }
857
858//    private final static Logger log = LoggerFactory.getLogger(CbusMessage.class);
859}