]> git.basschouten.com Git - openhab-addons.git/blob
4ca24bce91d35da1f8d95f3839b1c6674034bebe
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.insteon.internal.transport.message;
14
15 import java.util.Arrays;
16 import java.util.Optional;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.insteon.internal.device.DeviceAddress;
21 import org.openhab.binding.insteon.internal.device.InsteonAddress;
22 import org.openhab.binding.insteon.internal.device.X10Address;
23 import org.openhab.binding.insteon.internal.device.X10Flag;
24 import org.openhab.binding.insteon.internal.utils.BinaryUtils;
25 import org.openhab.binding.insteon.internal.utils.HexUtils;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * Contains an Insteon Message consisting of the raw data, and the message definition.
31  * For more info, see the public Insteon Developer's Guide, 2nd edition,
32  * and the Insteon Modem Developer's Guide.
33  *
34  * @author Bernd Pfrommer - Initial contribution
35  * @author Daniel Pfrommer - openHAB 1 insteonplm binding
36  * @author Rob Nielsen - Port to openHAB 2 insteon binding
37  * @author Jeremy Setton - Rewrite insteon binding
38  */
39 @NonNullByDefault
40 public class Msg {
41
42     public static enum Direction {
43         TO_MODEM,
44         FROM_MODEM
45     }
46
47     private final Logger logger = LoggerFactory.getLogger(Msg.class);
48
49     private byte[] data;
50     private int headerLength;
51     private Direction direction;
52     private MsgDefinition definition = new MsgDefinition();
53     private long quietTime = 0;
54     private boolean replayed = false;
55     private long timestamp = System.currentTimeMillis();
56
57     public Msg(int headerLength, int dataLength, Direction direction) {
58         this.data = new byte[dataLength];
59         this.headerLength = headerLength;
60         this.direction = direction;
61     }
62
63     public Msg(Msg msg, byte[] data, int dataLength) {
64         this.data = Arrays.copyOf(data, dataLength);
65         this.headerLength = msg.headerLength;
66         this.direction = msg.direction;
67         // the message definition usually doesn't change, but just to be sure...
68         this.definition = new MsgDefinition(msg.definition);
69     }
70
71     public Msg(Msg msg) {
72         this(msg, msg.data, msg.data.length);
73     }
74
75     public byte[] getData() {
76         return data;
77     }
78
79     public int getLength() {
80         return data.length;
81     }
82
83     public int getHeaderLength() {
84         return headerLength;
85     }
86
87     public Direction getDirection() {
88         return direction;
89     }
90
91     public MsgDefinition getDefinition() {
92         return definition;
93     }
94
95     public long getQuietTime() {
96         return quietTime;
97     }
98
99     public byte getCommand() {
100         try {
101             return getByte("Cmd");
102         } catch (FieldException e) {
103             return (byte) 0xFF;
104         }
105     }
106
107     public long getTimestamp() {
108         return timestamp;
109     }
110
111     public boolean isPureNack() {
112         return data.length == 2 && data[1] == 0x15;
113     }
114
115     public boolean isExtended() {
116         try {
117             return BinaryUtils.isBitSet(getInt("messageFlags"), 4);
118         } catch (FieldException e) {
119             return false;
120         }
121     }
122
123     public boolean isFromAddress(@Nullable InsteonAddress address) {
124         try {
125             return getInsteonAddress("fromAddress").equals(address);
126         } catch (FieldException e) {
127             return false;
128         }
129     }
130
131     public boolean isInbound() {
132         return direction == Direction.FROM_MODEM;
133     }
134
135     public boolean isOutbound() {
136         return direction == Direction.TO_MODEM;
137     }
138
139     public boolean isEcho() {
140         return isPureNack() || isReply();
141     }
142
143     public boolean isReply() {
144         return containsField("ACK/NACK");
145     }
146
147     public boolean isReplyAck() {
148         try {
149             return getByte("ACK/NACK") == 0x06;
150         } catch (FieldException e) {
151             return false;
152         }
153     }
154
155     public boolean isReplyNack() {
156         try {
157             return getByte("ACK/NACK") == 0x15;
158         } catch (FieldException e) {
159             return false;
160         }
161     }
162
163     public boolean isReplyOf(Msg msg) {
164         return isReply() && Arrays.equals(msg.getData(), Arrays.copyOf(data, msg.getLength()));
165     }
166
167     public boolean isFailureReport() {
168         return getCommand() == 0x5C;
169     }
170
171     public boolean isOfType(MsgType type) {
172         return type == getType();
173     }
174
175     public boolean isBroadcast() {
176         return isOfType(MsgType.BROADCAST);
177     }
178
179     public boolean isAllLinkBroadcast() {
180         return isOfType(MsgType.ALL_LINK_BROADCAST);
181     }
182
183     public boolean isAllLinkCleanup() {
184         return isOfType(MsgType.ALL_LINK_CLEANUP);
185     }
186
187     public boolean isAllLinkBroadcastOrCleanup() {
188         return isOfType(MsgType.ALL_LINK_BROADCAST) || isOfType(MsgType.ALL_LINK_CLEANUP);
189     }
190
191     public boolean isAllLinkCleanupAckOrNack() {
192         return isOfType(MsgType.ALL_LINK_CLEANUP_ACK) || isOfType(MsgType.ALL_LINK_CLEANUP_NACK);
193     }
194
195     public boolean isAllLinkSuccessReport() {
196         try {
197             return isOfType(MsgType.ALL_LINK_BROADCAST) && getByte("command1") == 0x06;
198         } catch (FieldException e) {
199             return false;
200         }
201     }
202
203     public boolean isDirect() {
204         return isOfType(MsgType.DIRECT);
205     }
206
207     public boolean isAckOfDirect() {
208         return isOfType(MsgType.ACK_OF_DIRECT);
209     }
210
211     public boolean isNackOfDirect() {
212         return isOfType(MsgType.NACK_OF_DIRECT);
213     }
214
215     public boolean isAckOrNackOfDirect() {
216         return isOfType(MsgType.ACK_OF_DIRECT) || isOfType(MsgType.NACK_OF_DIRECT);
217     }
218
219     public boolean isInsteon() {
220         return containsField("messageFlags");
221     }
222
223     public boolean isX10() {
224         return containsField("X10Flag");
225     }
226
227     public boolean isX10Address() {
228         try {
229             return getByte("X10Flag") == X10Flag.ADDRESS.code();
230         } catch (FieldException e) {
231             return false;
232         }
233     }
234
235     public boolean isX10Command() {
236         try {
237             return getByte("X10Flag") == X10Flag.COMMAND.code();
238         } catch (FieldException e) {
239             return false;
240         }
241     }
242
243     public boolean isReplayed() {
244         return replayed;
245     }
246
247     public void setDefinition(MsgDefinition definition) {
248         this.definition = definition;
249     }
250
251     public void setQuietTime(long quietTime) {
252         this.quietTime = quietTime;
253     }
254
255     public void setIsReplayed(boolean replayed) {
256         this.replayed = replayed;
257     }
258
259     public void addField(Field f) {
260         definition.addField(f);
261     }
262
263     public boolean containsField(String key) {
264         return definition.containsField(key);
265     }
266
267     public int getHopsLeft() {
268         try {
269             return (getByte("messageFlags") & 0x0C) >> 2;
270         } catch (FieldException e) {
271             return -1;
272         }
273     }
274
275     public int getMaxHops() {
276         try {
277             return getByte("messageFlags") & 0x03;
278         } catch (FieldException e) {
279             return -1;
280         }
281     }
282
283     /**
284      * Sets a byte at a specific field
285      *
286      * @param key the string key in the message definition
287      * @param value the byte to put
288      */
289     public void setByte(String key, byte value) throws FieldException {
290         Field field = definition.getField(key);
291         field.setByte(data, value);
292     }
293
294     /**
295      * Sets address bytes at a specific field
296      *
297      * @param key the name of the field
298      * @param address the address to put
299      */
300     public void setAddress(String key, DeviceAddress address) throws FieldException {
301         Field field = definition.getField(key);
302         if (address instanceof InsteonAddress insteonAddress) {
303             field.setAddress(data, insteonAddress);
304         } else if (address instanceof X10Address x10Address) {
305             field.setByte(data, x10Address.getCode());
306         }
307     }
308
309     /**
310      * Sets a byte array starting at a specific field
311      *
312      * @param key the name of the first field
313      */
314     public void setBytes(String key, byte[] bytes) throws FieldException {
315         int offset = definition.getField(key).getOffset();
316         if (offset < 0 || offset + bytes.length > data.length) {
317             throw new FieldException("data index out of bounds!");
318         }
319         System.arraycopy(bytes, 0, data, offset, bytes.length);
320     }
321
322     /**
323      * Sets a byte array starting at a specific field as an up to 32-bit integer
324      *
325      * @param key the name of the first field
326      * @param value the integer to put
327      * @param numBytes number of bytes to put
328      */
329     public void setInt(String key, int value, int numBytes) throws FieldException {
330         if (numBytes < 1 || numBytes > 4) {
331             throw new FieldException("number of bytes out of bounds!");
332         }
333         byte[] bytes = new byte[numBytes];
334         int shift = 8 * (numBytes - 1);
335         for (int i = 0; i < numBytes; i++) {
336             bytes[i] = (byte) (value >> shift);
337             shift -= 8;
338         }
339         setBytes(key, bytes);
340     }
341
342     /**
343      * Returns a byte from a specific field
344      *
345      * @param key the name of the field
346      * @return the byte
347      */
348     public byte getByte(String key) throws FieldException {
349         return definition.getField(key).getByte(data);
350     }
351
352     /**
353      * Returns the insteon address from a specific field
354      *
355      * @param key the name of the field
356      * @return the insteon address
357      */
358     public InsteonAddress getInsteonAddress(String key) throws FieldException {
359         return definition.getField(key).getAddress(data);
360     }
361
362     /**
363      * Returns the x10 address
364      *
365      * @return the x10 address
366      */
367     public @Nullable X10Address getX10Address() throws FieldException {
368         return isX10Address() ? new X10Address(getByte("rawX10")) : null;
369     }
370
371     /**
372      * Returns a byte array starting from a specific field
373      *
374      * @param key the name of the first field
375      * @param numBytes number of bytes to get
376      * @return the byte array
377      */
378     public byte[] getBytes(String key, int numBytes) throws FieldException {
379         int offset = definition.getField(key).getOffset();
380         if (offset < 0 || offset + numBytes > data.length) {
381             throw new FieldException("data index out of bounds!");
382         }
383         return Arrays.copyOfRange(data, offset, offset + numBytes);
384     }
385
386     /**
387      * Returns a byte array starting from a specific field as an up to 32-bit integer
388      *
389      * @param key the name of the first field
390      * @param numBytes number of bytes to use for conversion
391      * @return the integer
392      */
393     public int getInt(String key, int numBytes) throws FieldException {
394         if (numBytes < 1 || numBytes > 4) {
395             throw new FieldException("number of bytes out of bounds!");
396         }
397         int i = 0;
398         int shift = 8 * (numBytes - 1);
399         for (byte b : getBytes(key, numBytes)) {
400             i |= (b & 0xFF) << shift;
401             shift -= 8;
402         }
403         return i;
404     }
405
406     /**
407      * Returns a byte from a specific field as a 8-bit integer
408      *
409      * @param key the name of the field
410      * @return the integer
411      */
412     public int getInt(String key) throws FieldException {
413         return getByte(key) & 0xFF;
414     }
415
416     /**
417      * Returns a 2-byte array starting from a specific field as a 16-bit integer
418      *
419      * @param key the name of the first field
420      * @return the integer
421      */
422     public int getInt16(String key) throws FieldException {
423         return getInt(key, 2);
424     }
425
426     /**
427      * Returns a 3-byte array starting from a specific field as a 24-bit integer
428      *
429      * @param key the name of the first field
430      * @return the integer
431      */
432     public int getInt24(String key) throws FieldException {
433         return getInt(key, 3);
434     }
435
436     /**
437      * Returns a 4-byte array starting from a specific field as a 32-bit integer
438      *
439      * @param key the name of the first field
440      * @return the integer
441      */
442     public int getInt32(String key) throws FieldException {
443         return getInt(key, 4);
444     }
445
446     /**
447      * Returns a byte as a hex string
448      *
449      * @param key the name of the field
450      * @return the hex string
451      */
452     public String getHexString(String key) throws FieldException {
453         return HexUtils.getHexString(getByte(key));
454     }
455
456     /**
457      * Returns a byte array starting from a certain field as a hex string
458      *
459      * @param key the name of the field
460      * @param numBytes number of bytes to get
461      * @return the hex string
462      */
463     public String getHexString(String key, int numBytes) throws FieldException {
464         return HexUtils.getHexString(getBytes(key, numBytes), numBytes);
465     }
466
467     /**
468      * Returns group based on specific message characteristics
469      *
470      * @return group number if available, otherwise -1
471      */
472     public int getGroup() {
473         try {
474             if (isAllLinkBroadcast()) {
475                 return getInsteonAddress("toAddress").getLowByte() & 0xFF;
476             }
477             if (isAllLinkCleanup()) {
478                 return getInt("command2");
479             }
480             if (isExtended()) {
481                 byte cmd1 = getByte("command1");
482                 byte cmd2 = getByte("command2");
483                 // group number for specific extended msg located in userData1 byte
484                 if (cmd1 == 0x2E && cmd2 == 0x00) {
485                     return getInt("userData1");
486                 }
487             }
488         } catch (FieldException e) {
489             logger.warn("got field exception on msg: {}", e.getMessage());
490         }
491         return -1;
492     }
493
494     /**
495      * Returns msg type based on message flags
496      *
497      * @return msg type
498      */
499     public MsgType getType() {
500         try {
501             return MsgType.valueOf(getInt("messageFlags"));
502         } catch (FieldException | IllegalArgumentException e) {
503             return MsgType.INVALID;
504         }
505     }
506
507     /**
508      * Sets the userData fields from a byte array
509      *
510      * @param args list of user data arguments
511      */
512     public void setUserData(byte[] args) {
513         try {
514             for (int i = 0; i < 14; i++) {
515                 setByte("userData" + (i + 1), args.length > i ? args[i] : (byte) 0x00);
516             }
517         } catch (FieldException e) {
518             logger.warn("got field exception on msg {}:", e.getMessage());
519         }
520     }
521
522     /**
523      * Calculates the CRC using the older 1-byte method
524      *
525      * @return the calculated crc
526      * @throws FieldException
527      */
528     public int calculateCRC() throws FieldException {
529         int crc = 0;
530         byte[] bytes = getBytes("command1", 15); // skip userData14
531         for (byte b : bytes) {
532             crc += b;
533         }
534         return (~crc + 1) & 0xFF;
535     }
536
537     /**
538      * Calculates the CRC using the newer 2-byte method
539      *
540      * @return the calculated crc
541      * @throws FieldException
542      */
543     public int calculateCRC2() throws FieldException {
544         int crc = 0;
545         byte[] bytes = getBytes("command1", 14); // skip userData13/14
546         for (int loop = 0; loop < bytes.length; loop++) {
547             int b = bytes[loop] & 0xFF;
548             for (int bit = 0; bit < 8; bit++) {
549                 int fb = b & 0x01;
550                 if ((crc & 0x8000) == 0) {
551                     fb = fb ^ 0x01;
552                 }
553                 if ((crc & 0x4000) == 0) {
554                     fb = fb ^ 0x01;
555                 }
556                 if ((crc & 0x1000) == 0) {
557                     fb = fb ^ 0x01;
558                 }
559                 if ((crc & 0x0008) == 0) {
560                     fb = fb ^ 0x01;
561                 }
562                 crc = (crc << 1) | fb;
563                 b = b >> 1;
564             }
565         }
566         return crc & 0xFFFF;
567     }
568
569     /**
570      * Checks if message has a valid CRC using the older 1-byte method
571      *
572      * @return true if valid
573      */
574     public boolean hasValidCRC() {
575         try {
576             return getInt("userData14") == calculateCRC();
577         } catch (FieldException e) {
578             logger.warn("got field exception on msg {}:", e.getMessage());
579         }
580         return false;
581     }
582
583     /**
584      * Checks if message has a valid CRC using the newer 2-byte method is valid
585      *
586      * @return true if valid
587      */
588     public boolean hasValidCRC2() {
589         try {
590             return getInt16("userData13") == calculateCRC2();
591         } catch (FieldException e) {
592             logger.warn("got field exception on msg {}:", e.getMessage());
593         }
594         return false;
595     }
596
597     /**
598      * Sets the calculated CRC using the older 1-byte method
599      */
600     public void setCRC() {
601         try {
602             int crc = calculateCRC();
603             setByte("userData14", (byte) crc);
604         } catch (FieldException e) {
605             logger.warn("got field exception on msg {}:", e.getMessage());
606         }
607     }
608
609     /**
610      * Sets the calculated CRC using the newer 2-byte method
611      */
612     public void setCRC2() {
613         try {
614             int crc = calculateCRC2();
615             setByte("userData13", (byte) ((crc >> 8) & 0xFF));
616             setByte("userData14", (byte) (crc & 0xFF));
617         } catch (FieldException e) {
618             logger.warn("got field exception on msg {}:", e.getMessage());
619         }
620     }
621
622     @Override
623     public boolean equals(@Nullable Object obj) {
624         if (obj == this) {
625             return true;
626         }
627         if (obj == null) {
628             return false;
629         }
630         if (getClass() != obj.getClass()) {
631             return false;
632         }
633         Msg other = (Msg) obj;
634         return Arrays.equals(data, other.data);
635     }
636
637     @Override
638     public int hashCode() {
639         final int prime = 31;
640         int result = 1;
641         result = prime * result + Arrays.hashCode(data);
642         return result;
643     }
644
645     @Override
646     public String toString() {
647         String s = (direction == Direction.TO_MODEM) ? "OUT:" : "IN:";
648         for (Field field : definition.getFields()) {
649             if ("messageFlags".equals(field.getName())) {
650                 s += field.toString(data) + "=" + getType() + ":" + getHopsLeft() + ":" + getMaxHops() + "|";
651             } else {
652                 s += field.toString(data) + "|";
653             }
654         }
655         return s;
656     }
657
658     /**
659      * Factory method to create Msg from raw byte stream received from the serial port.
660      *
661      * @param buf the raw received bytes
662      * @param msgLen length of received buffer
663      * @param isExtended whether it is an extended message or not
664      * @return message, or null if the Msg cannot be created
665      */
666     public static @Nullable Msg createMessage(byte[] buf, int msgLen, boolean isExtended) {
667         if (buf.length < 2) {
668             return null;
669         }
670         return Optional
671                 .ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(buf[1], isExtended, Direction.FROM_MODEM))
672                 .filter(template -> template.getLength() == msgLen).map(template -> new Msg(template, buf, msgLen))
673                 .orElse(null);
674     }
675
676     /**
677      * Factory method to determine the header length of a received message
678      *
679      * @param cmd the message command received
680      * @return the length of the header to expect
681      */
682     public static int getHeaderLength(byte cmd) {
683         return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, Direction.FROM_MODEM))
684                 .map(Msg::getHeaderLength).orElse(-1);
685     }
686
687     /**
688      * Factory method to determine the length of a received message
689      *
690      * @param cmd the message command received
691      * @param isExtended if is an extended message
692      * @return message length, or -1 if length cannot be determined
693      */
694     public static int getMessageLength(byte cmd, boolean isExtended) {
695         return Optional
696                 .ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, isExtended, Direction.FROM_MODEM))
697                 .map(Msg::getLength).orElse(-1);
698     }
699
700     /**
701      * Factory method to determine if a message is extended
702      *
703      * @param buf the received bytes
704      * @param len the number of bytes received so far
705      * @param headerLength the known length of the header
706      * @return true if it is definitely extended, false if cannot be
707      *         determined or if it is a standard message
708      */
709     public static boolean isExtended(byte[] buf, int len, int headerLength) {
710         if (headerLength <= 2) {
711             return false;
712         } // extended messages are longer
713         if (len < headerLength) {
714             return false;
715         } // not enough data to tell if extended
716         byte flags = buf[headerLength - 1]; // last byte says flags
717         boolean isExtended = BinaryUtils.isBitSet(flags & 0xFF, 4);
718         return isExtended;
719     }
720
721     /**
722      * Factory method to create a message to send for a given cmd
723      *
724      * @param cmd the message cmd to create, as defined in the xml file
725      * @return the insteon message
726      * @throws InvalidMessageTypeException
727      */
728     public static Msg makeMessage(byte cmd) throws InvalidMessageTypeException {
729         return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, Direction.TO_MODEM))
730                 .map(Msg::new).orElseThrow(() -> new InvalidMessageTypeException(
731                         "unknown message command: " + HexUtils.getHexString(cmd)));
732     }
733
734     /**
735      * Factory method to create an Insteon message to send for a given type
736      *
737      * @param type the message type to create, as defined in the xml file
738      * @return the insteon message
739      * @throws InvalidMessageTypeException
740      */
741     public static Msg makeMessage(String type) throws InvalidMessageTypeException {
742         return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(type)).map(Msg::new)
743                 .orElseThrow(() -> new InvalidMessageTypeException("unknown message type: " + type));
744     }
745
746     /**
747      * Factory method to create a broadcast message to send
748      *
749      * @param group the broadcast group to send the message to
750      * @param cmd1 the message command 1 field
751      * @param cmd2 the message command 2 field
752      * @return the broadcast message
753      * @throws FieldException
754      * @throws InvalidMessageTypeException
755      */
756     public static Msg makeBroadcastMessage(int group, byte cmd1, byte cmd2)
757             throws FieldException, InvalidMessageTypeException {
758         Msg msg = makeMessage("SendStandardMessage");
759         msg.setAddress("toAddress", new InsteonAddress((byte) 0, (byte) 0, (byte) (group & 0xFF)));
760         msg.setByte("messageFlags", (byte) 0xCF);
761         msg.setByte("command1", cmd1);
762         msg.setByte("command2", cmd2);
763         msg.setQuietTime(0L);
764         return msg;
765     }
766
767     /**
768      * Factory method to create a standard message to send
769      *
770      * @param address the address to send the message to
771      * @param cmd1 the message command 1 field
772      * @param cmd2 the message command 2 field
773      * @return the standard message
774      * @throws FieldException
775      * @throws InvalidMessageTypeException
776      */
777     public static Msg makeStandardMessage(InsteonAddress address, byte cmd1, byte cmd2)
778             throws FieldException, InvalidMessageTypeException {
779         return makeStandardMessage(address, (byte) 0x0F, cmd1, cmd2);
780     }
781
782     /**
783      * Factory method to create a standard message to send
784      *
785      * @param address the address to send the message to
786      * @param flags the message flags field
787      * @param cmd1 the message command 1 field
788      * @param cmd2 the message command 2 field
789      * @return the standard message
790      * @throws FieldException
791      * @throws InvalidMessageTypeException
792      */
793     public static Msg makeStandardMessage(InsteonAddress address, byte flags, byte cmd1, byte cmd2)
794             throws FieldException, InvalidMessageTypeException {
795         Msg msg = makeMessage("SendStandardMessage");
796         msg.setAddress("toAddress", address);
797         msg.setByte("messageFlags", flags);
798         msg.setByte("command1", cmd1);
799         msg.setByte("command2", cmd2);
800         // set default quiet time accounting for ack response
801         msg.setQuietTime(1000L);
802         return msg;
803     }
804
805     /**
806      * Factory method to create an extended message to send with optional CRC
807      *
808      * @param address the address to send the message to
809      * @param cmd1 the message command 1 field
810      * @param cmd2 the message command 2 field
811      * @param setCRC if the CRC should be set
812      * @return extended message
813      * @throws FieldException
814      * @throws InvalidMessageTypeException
815      */
816     public static Msg makeExtendedMessage(InsteonAddress address, byte cmd1, byte cmd2, boolean setCRC)
817             throws FieldException, InvalidMessageTypeException {
818         return makeExtendedMessage(address, cmd1, cmd2, new byte[] {}, setCRC);
819     }
820
821     /**
822      * Factory method to create an extended message to send with specific user data and optional CRC
823      *
824      * @param address the address to send the message to
825      * @param cmd1 the message command 1 field
826      * @param cmd2 the message command 2 field
827      * @param data the message user data fields
828      * @param setCRC if the CRC should be set
829      * @return extended message
830      * @throws FieldException
831      * @throws InvalidMessageTypeException
832      */
833     public static Msg makeExtendedMessage(InsteonAddress address, byte cmd1, byte cmd2, byte[] data, boolean setCRC)
834             throws FieldException, InvalidMessageTypeException {
835         return makeExtendedMessage(address, (byte) 0x1F, cmd1, cmd2, data, setCRC);
836     }
837
838     /**
839      * Factory method to create an extended message to send with specific user data and optional CRC
840      *
841      * @param address the address to send the message to
842      * @param flags the message flags field
843      * @param cmd1 the message command 1 field
844      * @param cmd2 the message command 2 field
845      * @param data the message user data fields
846      * @param setCRC if the CRC should be set
847      * @return extended message
848      * @throws FieldException
849      * @throws InvalidMessageTypeException
850      */
851     public static Msg makeExtendedMessage(InsteonAddress address, byte flags, byte cmd1, byte cmd2, byte[] data,
852             boolean setCRC) throws FieldException, InvalidMessageTypeException {
853         Msg msg = makeMessage("SendExtendedMessage");
854         msg.setAddress("toAddress", address);
855         msg.setByte("messageFlags", (byte) (flags | 0x10));
856         msg.setByte("command1", cmd1);
857         msg.setByte("command2", cmd2);
858         msg.setUserData(data);
859         if (setCRC) {
860             msg.setCRC();
861         }
862         // set default quiet time accounting for ack followed by direct response messages
863         msg.setQuietTime(2000L);
864         return msg;
865     }
866
867     /**
868      * Factory method to create an extended message to send with specific user data and CRC2
869      *
870      * @param address the address to send the message to
871      * @param cmd1 the message command 1 field
872      * @param cmd2 the message command 2 field
873      * @param data the message user data fields
874      * @return extended message
875      * @throws FieldException
876      * @throws InvalidMessageTypeException
877      */
878     public static Msg makeExtendedMessageCRC2(InsteonAddress address, byte cmd1, byte cmd2, byte[] data)
879             throws FieldException, InvalidMessageTypeException {
880         Msg msg = Msg.makeExtendedMessage(address, cmd1, cmd2, data, false);
881         msg.setCRC2();
882         return msg;
883     }
884
885     /**
886      * Factory method to create an X10 message to send
887      *
888      * @param cmd the X10 command
889      * @param flag the X10 flag
890      * @return the X10 message
891      * @throws FieldException
892      * @throws InvalidMessageTypeException
893      */
894     public static Msg makeX10Message(byte cmd, byte flag) throws FieldException, InvalidMessageTypeException {
895         Msg msg = makeMessage("SendX10Message");
896         msg.setByte("rawX10", cmd);
897         msg.setByte("X10Flag", flag);
898         msg.setQuietTime(300L);
899         return msg;
900     }
901
902     /**
903      * Factory method to create an X10 address message to send
904      *
905      * @param address the X10 address
906      * @return the X10 address message
907      * @throws FieldException
908      * @throws InvalidMessageTypeException
909      */
910     public static Msg makeX10AddressMessage(X10Address address) throws FieldException, InvalidMessageTypeException {
911         return makeX10Message(address.getCode(), X10Flag.ADDRESS.code());
912     }
913
914     /**
915      * Factory method to create an X10 command message to send
916      *
917      * @param cmd the X10 command
918      * @return the X10 command message
919      * @throws FieldException
920      * @throws InvalidMessageTypeException
921      */
922     public static Msg makeX10CommandMessage(byte cmd) throws FieldException, InvalidMessageTypeException {
923         return makeX10Message(cmd, X10Flag.COMMAND.code());
924     }
925 }