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