import org.openhab.binding.insteon.internal.device.X10Address;
import org.openhab.binding.insteon.internal.device.X10Device;
import org.openhab.binding.insteon.internal.transport.PortListener;
+import org.openhab.binding.insteon.internal.transport.message.Direction;
import org.openhab.binding.insteon.internal.transport.message.FieldException;
import org.openhab.binding.insteon.internal.transport.message.InvalidMessageTypeException;
import org.openhab.binding.insteon.internal.transport.message.Msg;
-import org.openhab.binding.insteon.internal.transport.message.Msg.Direction;
import org.openhab.binding.insteon.internal.transport.message.MsgDefinitionRegistry;
import org.openhab.binding.insteon.internal.utils.HexUtils;
import org.openhab.core.io.console.Console;
int deviceCategory = msg.getInt("DeviceCategory");
int subCategory = msg.getInt("DeviceSubCategory");
- ProductData productData = ProductDataRegistry.getInstance().getProductData(deviceCategory, subCategory);
+ ProductData productData = ProductData.makeInsteonProduct(deviceCategory, subCategory);
productData.setFirmwareVersion(msg.getInt("FirmwareVersion"));
DeviceType deviceType = productData.getDeviceType();
}
public State getState() {
- return getEntries().stream().allMatch(entry -> entry.getState() == UnDefType.NULL) ? UnDefType.NULL
- : OnOffType.from(getEntries().stream().filter(entry -> entry.getState() != UnDefType.NULL)
- .allMatch(entry -> entry.getState().equals(entry.getOnState())));
+ return getEntries().stream().noneMatch(SceneEntry::isStateDefined) ? UnDefType.NULL
+ : OnOffType
+ .from(getEntries().stream().filter(SceneEntry::isStateDefined).allMatch(SceneEntry::isStateOn));
}
public boolean hasEntry(InsteonAddress address) {
return state;
}
+ public boolean isStateDefined() {
+ return !UnDefType.NULL.equals(state);
+ }
+
+ public boolean isStateOn() {
+ return getOnState().equals(state);
+ }
+
public void setState(State state) {
this.state = state;
}
ProductData productData = new ProductData();
productData.setDeviceCategory(deviceCategory);
productData.setSubCategory(subCategory);
- return productData;
- }
-
- /**
- * Factory method for creating a ProductData for an Insteon product
- *
- * @param deviceCategory the Insteon device category
- * @param subCategory the Insteon device subcategory
- * @param srcData the source product data to use
- * @return the product data
- */
- public static ProductData makeInsteonProduct(int deviceCategory, int subCategory, @Nullable ProductData srcData) {
- ProductData productData = makeInsteonProduct(deviceCategory, subCategory);
- if (srcData != null) {
- productData.update(srcData);
+ ProductData resourceData = ProductDataRegistry.getInstance().getProductData(deviceCategory, subCategory);
+ if (resourceData != null) {
+ productData.update(resourceData);
}
return productData;
}
*
* @param deviceCategory device category to match
* @param subCategory device subcategory to match
- * @return product data matching provided parameters
+ * @return product data if matching provided parameters, otherwise null
*/
- public ProductData getProductData(int deviceCategory, int subCategory) {
+ public @Nullable ProductData getProductData(int deviceCategory, int subCategory) {
int productId = getProductId(deviceCategory, subCategory);
if (!products.containsKey(productId)) {
logger.warn("unknown product for devCat:{} subCat:{} in device products xml file",
// fallback to matching product id using device category only
productId = getProductId(deviceCategory, ProductData.SUB_CATEGORY_UNKNOWN);
}
-
- return ProductData.makeInsteonProduct(deviceCategory, subCategory, products.get(productId));
- }
-
- /**
- * Returns the device type for a given dev/sub category
- *
- * @param deviceCategory device category to match
- * @param subCategory device subcategory to match
- * @return device type matching provided parameters
- */
- public @Nullable DeviceType getDeviceType(int deviceCategory, int subCategory) {
- return getProductData(deviceCategory, subCategory).getDeviceType();
+ return products.get(productId);
}
/**
logger.warn("overwriting previous definition of product {}", products.get(productId));
}
- ProductData productData = ProductData.makeInsteonProduct(deviceCategory, subCategory);
+ ProductData productData = new ProductData();
+ productData.setDeviceCategory(deviceCategory);
+ productData.setSubCategory(subCategory);
productData.setProductKey(productKey);
productData.setFirstRecordLocation(firstRecord);
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
SEC_0_2(0x1E, 0.2),
INSTANT(0x1F, 0.1);
+ private static final List<String> SUPPORTED_FEATURE_TYPES = List.of(FEATURE_TYPE_GENERIC_DIMMER);
+
private static final Map<Integer, RampRate> VALUE_MAP = Arrays.stream(values())
.collect(Collectors.toUnmodifiableMap(rate -> rate.value, Function.identity()));
* @return true if supported
*/
public static boolean supportsFeatureType(String featureType) {
- return FEATURE_TYPE_GENERIC_DIMMER.equals(featureType);
+ return SUPPORTED_FEATURE_TYPES.contains(featureType);
}
/**
import org.openhab.binding.insteon.internal.device.InsteonAddress;
import org.openhab.binding.insteon.internal.device.InsteonModem;
import org.openhab.binding.insteon.internal.device.ProductData;
-import org.openhab.binding.insteon.internal.device.ProductDataRegistry;
import org.openhab.binding.insteon.internal.transport.PortListener;
import org.openhab.binding.insteon.internal.transport.message.FieldException;
import org.openhab.binding.insteon.internal.transport.message.InvalidMessageTypeException;
int subCategory = Byte.toUnsignedInt(toAddr.getMiddleByte());
int firmware = Byte.toUnsignedInt(toAddr.getLowByte());
int hardware = msg.getInt("command2");
- ProductData productData = ProductDataRegistry.getInstance().getProductData(deviceCategory, subCategory);
+ ProductData productData = ProductData.makeInsteonProduct(deviceCategory, subCategory);
productData.setFirmwareVersion(firmware);
productData.setHardwareVersion(hardware);
// set product data if in modem db
public void discoverInsteonDevice(InsteonAddress address, @Nullable ProductData productData) {
InsteonModem modem = handler.getModem();
- if (handler.isDeviceDiscoveryEnabled() && modem != null && modem.getDB().hasEntry(address)
- && !modem.hasDevice(address)) {
+ if (modem != null && modem.getDB().hasEntry(address) && !modem.hasDevice(address)) {
addInsteonDevice(address, productData);
} else {
removeInsteonDevice(address);
public void discoverInsteonScene(int group) {
InsteonModem modem = handler.getModem();
- if (handler.isSceneDiscoveryEnabled() && modem != null && modem.getDB().hasBroadcastGroup(group)
- && !modem.hasScene(group)) {
+ if (modem != null && modem.getDB().hasBroadcastGroup(group) && !modem.hasScene(group)) {
addInsteonScene(group);
} else {
removeInsteonScene(group);
protected void discoverInsteonDevice(InsteonAddress address, @Nullable ProductData productData) {
InsteonDiscoveryService discoveryService = getDiscoveryService();
- if (discoveryService != null) {
+ if (discoveryService != null && isDeviceDiscoveryEnabled()) {
scheduler.execute(() -> discoveryService.discoverInsteonDevice(address, productData));
}
}
protected void discoverInsteonScene(int group) {
InsteonDiscoveryService discoveryService = getDiscoveryService();
- if (discoveryService != null) {
+ if (discoveryService != null && isSceneDiscoveryEnabled()) {
scheduler.execute(() -> discoveryService.discoverInsteonScene(group));
}
}
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
/**
* Defines the data types that can be used in the fields of a message.
@NonNullByDefault
public enum DataType {
BYTE("byte", 1),
- ADDRESS("address", 3),
- INVALID("invalid", -1);
+ ADDRESS("address", 3);
private static final Map<String, DataType> NAME_MAP = Arrays.stream(values())
.collect(Collectors.toUnmodifiableMap(type -> type.name, Function.identity()));
* Factory method for getting a DataType from the data type name
*
* @param name the data type name
- * @return the data type
+ * @return the data type if defined, otherwise null
*/
- public static DataType get(String name) {
- return NAME_MAP.getOrDefault(name, DataType.INVALID);
+ public static @Nullable DataType get(String name) {
+ return NAME_MAP.get(name);
}
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.insteon.internal.transport.message;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link Direction} represents a message direction
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public enum Direction {
+ FROM_MODEM("IN"),
+ TO_MODEM("OUT");
+
+ private final String label;
+
+ private Direction(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+
+ public static @Nullable Direction get(String value) {
+ try {
+ return Direction.valueOf(value);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
private final int offset;
private final DataType type;
+ public Field(String name, int offset, DataType type) {
+ this.name = name;
+ this.offset = offset;
+ this.type = type;
+ }
+
public String getName() {
return name;
}
return offset;
}
- public DataType getType() {
- return type;
- }
-
- public Field(String name, DataType type, int offset) {
- this.name = name;
- this.type = type;
- this.offset = offset;
- }
-
private void check(int len, DataType t) throws FieldException {
if (offset + type.getSize() > len) {
throw new FieldException("field write beyond end of msg");
}
}
- public void set(byte[] data, Object o) throws FieldException {
+ public void set(byte[] data, String value) throws FieldException, IllegalArgumentException {
switch (type) {
case BYTE:
- setByte(data, (Byte) o);
+ byte b = value.isEmpty() ? 0x00 : (byte) HexUtils.toInteger(value);
+ setByte(data, b);
break;
case ADDRESS:
- setAddress(data, (InsteonAddress) o);
+ InsteonAddress address = value.isEmpty() ? InsteonAddress.UNKNOWN : new InsteonAddress(value);
+ setAddress(data, address);
break;
- default:
- throw new FieldException("field data type unknown");
}
}
case ADDRESS:
s += getAddress(data).toString();
break;
- default:
- throw new FieldException("field data type unknown");
}
} catch (FieldException e) {
s += "NULL";
*/
@NonNullByDefault
public class Msg {
-
- public static enum Direction {
- TO_MODEM,
- FROM_MODEM
- }
-
private final Logger logger = LoggerFactory.getLogger(Msg.class);
- private byte[] data;
- private int headerLength;
- private Direction direction;
- private MsgDefinition definition = new MsgDefinition();
+ private final byte[] data;
+ private final MsgDefinition definition;
private long quietTime = 0;
private boolean replayed = false;
private long timestamp = System.currentTimeMillis();
- public Msg(int headerLength, int dataLength, Direction direction) {
- this.data = new byte[dataLength];
- this.headerLength = headerLength;
- this.direction = direction;
- }
-
- public Msg(Msg msg, byte[] data, int dataLength) {
- this.data = Arrays.copyOf(data, dataLength);
- this.headerLength = msg.headerLength;
- this.direction = msg.direction;
- // the message definition usually doesn't change, but just to be sure...
- this.definition = new MsgDefinition(msg.definition);
+ public Msg(byte[] data, MsgDefinition definition) {
+ this.data = Arrays.copyOf(data, definition.getLength());
+ this.definition = definition;
}
- public Msg(Msg msg) {
- this(msg, msg.data, msg.data.length);
+ public Msg(MsgDefinition definition) {
+ this(definition.getData(), definition);
}
public byte[] getData() {
}
public int getHeaderLength() {
- return headerLength;
+ return definition.getHeaderLength();
}
public Direction getDirection() {
- return direction;
- }
-
- public MsgDefinition getDefinition() {
- return definition;
+ return definition.getDirection();
}
public long getQuietTime() {
}
public boolean isInbound() {
- return direction == Direction.FROM_MODEM;
+ return getDirection() == Direction.FROM_MODEM;
}
public boolean isOutbound() {
- return direction == Direction.TO_MODEM;
+ return getDirection() == Direction.TO_MODEM;
}
public boolean isEcho() {
return replayed;
}
- public void setDefinition(MsgDefinition definition) {
- this.definition = definition;
- }
-
public void setQuietTime(long quietTime) {
this.quietTime = quietTime;
}
this.replayed = replayed;
}
- public void addField(Field f) {
- definition.addField(f);
- }
-
public boolean containsField(String key) {
return definition.containsField(key);
}
/**
* Sets a byte at a specific field
*
- * @param key the string key in the message definition
+ * @param key the name of the field
* @param value the byte to put
*/
public void setByte(String key, byte value) throws FieldException {
- Field field = definition.getField(key);
- field.setByte(data, value);
+ definition.getField(key).setByte(data, value);
}
/**
return getInt(key, 4);
}
- /**
- * Returns a byte as a hex string
- *
- * @param key the name of the field
- * @return the hex string
- */
- public String getHexString(String key) throws FieldException {
- return HexUtils.getHexString(getByte(key));
- }
-
- /**
- * Returns a byte array starting from a certain field as a hex string
- *
- * @param key the name of the field
- * @param numBytes number of bytes to get
- * @return the hex string
- */
- public String getHexString(String key, int numBytes) throws FieldException {
- return HexUtils.getHexString(getBytes(key, numBytes), numBytes);
- }
-
/**
* Returns group based on specific message characteristics
*
@Override
public String toString() {
- String s = (direction == Direction.TO_MODEM) ? "OUT:" : "IN:";
+ String s = definition.getDirection() + ":";
for (Field field : definition.getFields()) {
if ("messageFlags".equals(field.getName())) {
s += field.toString(data) + "=" + getType() + ":" + getHopsLeft() + ":" + getMaxHops() + "|";
return null;
}
return Optional
- .ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(buf[1], isExtended, Direction.FROM_MODEM))
- .filter(template -> template.getLength() == msgLen).map(template -> new Msg(template, buf, msgLen))
+ .ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(buf[1], isExtended, Direction.FROM_MODEM))
+ .filter(definition -> definition.getLength() == msgLen).map(definition -> new Msg(buf, definition))
.orElse(null);
}
*
* @param cmd the message command received
* @return the length of the header to expect
+ * @throws InvalidMessageTypeException
*/
- public static int getHeaderLength(byte cmd) {
- return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, Direction.FROM_MODEM))
- .map(Msg::getHeaderLength).orElse(-1);
+ public static int getHeaderLength(byte cmd) throws InvalidMessageTypeException {
+ return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(cmd, Direction.FROM_MODEM))
+ .map(MsgDefinition::getHeaderLength)
+ .orElseThrow(() -> new InvalidMessageTypeException("unknown message command"));
}
/**
* @param cmd the message command received
* @param isExtended if is an extended message
* @return message length, or -1 if length cannot be determined
+ * @throws InvalidMessageTypeException
*/
- public static int getMessageLength(byte cmd, boolean isExtended) {
+ public static int getMessageLength(byte cmd, boolean isExtended) throws InvalidMessageTypeException {
return Optional
- .ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, isExtended, Direction.FROM_MODEM))
- .map(Msg::getLength).orElse(-1);
+ .ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(cmd, isExtended, Direction.FROM_MODEM))
+ .map(MsgDefinition::getLength)
+ .orElseThrow(() -> new InvalidMessageTypeException("unknown message command"));
}
/**
* Factory method to determine if a message is extended
*
* @param buf the received bytes
- * @param len the number of bytes received so far
- * @param headerLength the known length of the header
- * @return true if it is definitely extended, false if cannot be
- * determined or if it is a standard message
+ * @param headerLength the header length
+ * @return true if is extended
*/
- public static boolean isExtended(byte[] buf, int len, int headerLength) {
- if (headerLength <= 2) {
+ public static boolean isExtended(byte[] buf, int headerLength) {
+ if (headerLength <= 2 || buf.length < headerLength) {
return false;
- } // extended messages are longer
- if (len < headerLength) {
- return false;
- } // not enough data to tell if extended
- byte flags = buf[headerLength - 1]; // last byte says flags
- boolean isExtended = BinaryUtils.isBitSet(flags & 0xFF, 4);
- return isExtended;
+ }
+ return BinaryUtils.isBitSet(buf[headerLength - 1] & 0xFF, 4);
}
/**
* @throws InvalidMessageTypeException
*/
public static Msg makeMessage(byte cmd) throws InvalidMessageTypeException {
- return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(cmd, Direction.TO_MODEM))
+ return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(cmd, Direction.TO_MODEM))
.map(Msg::new).orElseThrow(() -> new InvalidMessageTypeException(
"unknown message command: " + HexUtils.getHexString(cmd)));
}
* @throws InvalidMessageTypeException
*/
public static Msg makeMessage(String type) throws InvalidMessageTypeException {
- return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getTemplate(type)).map(Msg::new)
+ return Optional.ofNullable(MsgDefinitionRegistry.getInstance().getDefinition(type)).map(Msg::new)
.orElseThrow(() -> new InvalidMessageTypeException("unknown message type: " + type));
}
*/
package org.openhab.binding.insteon.internal.transport.message;
-import java.util.Comparator;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.insteon.internal.utils.BinaryUtils;
/**
* Definition (layout) of an Insteon message. Says which bytes go where.
*/
@NonNullByDefault
public class MsgDefinition {
- private Map<String, Field> fields = new HashMap<>();
+ private final byte[] data;
+ private final int headerLength;
+ private final Direction direction;
+ private final Map<String, Field> fields;
- MsgDefinition() {
+ public MsgDefinition(byte[] data, int headerLength, Direction direction, Map<String, Field> fields) {
+ this.data = data;
+ this.headerLength = headerLength;
+ this.direction = direction;
+ this.fields = fields;
}
- MsgDefinition(MsgDefinition definition) {
- fields = new HashMap<>(definition.fields);
+ public byte[] getData() {
+ return data;
}
- public List<Field> getFields() {
- return fields.values().stream().sorted(Comparator.comparing(Field::getOffset)).toList();
+ public int getLength() {
+ return data.length;
}
- public boolean containsField(String name) {
- return fields.containsKey(name);
+ public int getHeaderLength() {
+ return headerLength;
}
- public void addField(Field field) {
- fields.put(field.getName(), field);
+ public Direction getDirection() {
+ return direction;
}
- /**
- * Finds field of a given name
- *
- * @param name name of the field to search for
- * @return reference to field
- * @throws FieldException if no such field can be found
- */
public Field getField(String name) throws FieldException {
Field field = fields.get(name);
if (field == null) {
}
return field;
}
+
+ public List<Field> getFields() {
+ return fields.values().stream().toList();
+ }
+
+ public boolean containsField(String name) {
+ return fields.containsKey(name);
+ }
+
+ public byte getByte(String name) throws FieldException {
+ return getField(name).getByte(data);
+ }
+
+ public byte getCommand() {
+ try {
+ return getByte("Cmd");
+ } catch (FieldException e) {
+ return (byte) 0xFF;
+ }
+ }
+
+ public boolean isExtended() {
+ try {
+ return BinaryUtils.isBitSet(getByte("messageFlags"), 4);
+ } catch (FieldException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ String s = direction + ":";
+ for (Field field : getFields()) {
+ s += field.toString(data) + "|";
+ }
+ return s;
+ }
}
import java.util.LinkedHashMap;
import java.util.Map;
-import java.util.Map.Entry;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.insteon.internal.InsteonResourceLoader;
-import org.openhab.binding.insteon.internal.device.InsteonAddress;
-import org.openhab.binding.insteon.internal.transport.message.Msg.Direction;
-import org.openhab.binding.insteon.internal.utils.HexUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
private static final MsgDefinitionRegistry MSG_DEFINITION_REGISTRY = new MsgDefinitionRegistry();
private static final String RESOURCE_NAME = "/msg-definitions.xml";
- private Map<String, Msg> definitions = new LinkedHashMap<>();
+ private Map<String, MsgDefinition> definitions = new LinkedHashMap<>();
private MsgDefinitionRegistry() {
super(RESOURCE_NAME);
}
/**
- * Returns message template for a given type
+ * Returns message definition for a given type
*
* @param type message type to match
- * @return message template if found, otherwise null
+ * @return message definition if found, otherwise null
*/
- public @Nullable Msg getTemplate(String type) {
+ public @Nullable MsgDefinition getDefinition(String type) {
return definitions.get(type);
}
/**
- * Returns message template for a given command and direction
+ * Returns message definition for a given command and direction
*
* @param cmd message command to match
* @param direction message direction to match
- * @return message template if found, otherwise null
+ * @return message definition if found, otherwise null
*/
- public @Nullable Msg getTemplate(byte cmd, Direction direction) {
- return getTemplate(cmd, null, direction);
+ public @Nullable MsgDefinition getDefinition(byte cmd, Direction direction) {
+ return getDefinition(cmd, null, direction);
}
/**
- * Returns message template for a given command, extended flag and direction
+ * Returns message definition for a given command, extended flag and direction
*
* @param cmd message command to match
* @param isExtended if message is extended
* @param direction message direction to match
- * @return message template if found, otherwise null
+ * @return message definition if found, otherwise null
*/
- public @Nullable Msg getTemplate(byte cmd, @Nullable Boolean isExtended, Direction direction) {
- return definitions.values().stream().filter(msg -> msg.getCommand() == cmd && msg.getDirection() == direction
- && (isExtended == null || msg.isExtended() == isExtended)).findFirst().orElse(null);
+ public @Nullable MsgDefinition getDefinition(byte cmd, @Nullable Boolean isExtended, Direction direction) {
+ return definitions.values().stream()
+ .filter(definition -> definition.getCommand() == cmd && definition.getDirection() == direction
+ && (isExtended == null || definition.isExtended() == isExtended))
+ .findFirst().orElse(null);
}
/**
*
* @return currently known message definitions
*/
- public Map<String, Msg> getDefinitions() {
+ public Map<String, MsgDefinition> getDefinitions() {
return definitions;
}
* @throws SAXException
*/
private void parseMsgDefinition(Element element) throws SAXException {
- LinkedHashMap<Field, Object> fields = new LinkedHashMap<>();
String name = element.getAttribute("name");
- Direction direction = Direction.valueOf(element.getAttribute("direction"));
- int length = element.hasAttribute("length") ? Integer.parseInt(element.getAttribute("length")) : 0;
+ if (name.isEmpty()) {
+ throw new SAXException("undefined message definition name");
+ }
+ Direction direction = Direction.get(element.getAttribute("direction"));
+ if (direction == null) {
+ throw new SAXException("invalid direction for message definition " + name);
+ }
+ int length = getAttributeAsInteger(element, "length", 0);
+ if (length == 0) {
+ throw new SAXException("undefined length for message definition " + name);
+ }
int headerLength = 0;
int offset = 0;
+ byte[] data = new byte[length];
+ Map<String, Field> fields = new LinkedHashMap<>();
NodeList nodes = element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element child = (Element) node;
String nodeName = child.getNodeName();
- if ("header".equals(nodeName)) {
- headerLength = parseHeader(child, fields);
- // Increment the offset by the header length
- offset += headerLength;
- } else {
+ if (!"header".equals(nodeName)) {
// Increment the offset by the field data type length
- offset += parseField(child, offset, fields);
+ offset += parseField(child, offset, data, fields);
+ } else if (offset == 0) {
+ headerLength = parseHeader(child, data, fields);
+ // Set the offset to the header length
+ offset = headerLength;
}
}
}
- if (length == 0) {
- length = offset;
- } else if (offset != length) {
- throw new SAXException("actual msg length " + offset + " differs from given msg length " + length);
+ if (headerLength == 0) {
+ throw new SAXException("undefined header for message definition " + name);
+ }
+ if (offset != length) {
+ throw new SAXException("actual msg length " + offset + " differs from given length " + length);
}
- try {
- Msg msg = makeMsgTemplate(fields, headerLength, length, direction);
- definitions.put(name, msg);
- } catch (FieldException e) {
- throw new SAXException("failed to create message definition " + name + ":", e);
+ MsgDefinition definition = new MsgDefinition(data, headerLength, direction, fields);
+ if (definitions.put(name, definition) != null) {
+ logger.warn("duplicate message definition {}", name);
}
}
* Parses header node
*
* @param element element to parse
+ * @param data msg data to update
* @param fields fields map to update
* @return header length
* @throws SAXException
*/
- private int parseHeader(Element element, LinkedHashMap<Field, Object> fields) throws SAXException {
- int length = Integer.parseInt(element.getAttribute("length"));
+ private int parseHeader(Element element, byte[] data, Map<String, Field> fields) throws SAXException {
+ int length = getAttributeAsInteger(element, "length", 0);
+ if (length == 0) {
+ throw new SAXException("undefined header length");
+ }
int offset = 0;
NodeList nodes = element.getChildNodes();
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element child = (Element) node;
// Increment the offset by the field data type length
- offset += parseField(child, offset, fields);
+ offset += parseField(child, offset, data, fields);
}
}
- if (length != offset) {
+ if (offset != length) {
throw new SAXException("actual header length " + offset + " differs from given length " + length);
}
return length;
*
* @param element element to parse
* @param offset msg offset
+ * @param data msg data to update
* @param fields fields map to update
* @return field data type length
* @throws SAXException
*/
- private int parseField(Element element, int offset, LinkedHashMap<Field, Object> fields) throws SAXException {
+ private int parseField(Element element, int offset, byte[] data, Map<String, Field> fields) throws SAXException {
String name = element.getAttribute("name");
- if (name == null) {
- throw new SAXException("undefined field name");
- }
DataType dataType = DataType.get(element.getNodeName());
- Field field = new Field(name, dataType, offset);
- Object value = getFieldValue(dataType, element.getTextContent().trim());
- fields.put(field, value);
- return dataType.getSize();
- }
-
- /**
- * Returns field value
- *
- * @param dataType field data type
- * @param value value to convert
- * @return field value
- * @throws SAXException
- */
- private Object getFieldValue(DataType dataType, String value) throws SAXException {
- switch (dataType) {
- case BYTE:
- return getByteValue(value);
- case ADDRESS:
- return getAddressValue(value);
- default:
- throw new SAXException("invalid field data type");
- }
- }
-
- /**
- * Returns field value as a byte
- *
- * @param value value to convert
- * @return byte
- * @throws SAXException
- */
- private byte getByteValue(String value) throws SAXException {
- try {
- return value.isEmpty() ? 0x00 : (byte) HexUtils.toInteger(value);
- } catch (NumberFormatException e) {
- throw new SAXException("invalid field byte value: " + value);
+ if (dataType == null) {
+ throw new SAXException("invalid field data type");
}
- }
-
- /**
- * Returns field value as an insteon address
- *
- * @param value value to convert
- * @return insteon address
- * @throws SAXException
- */
- private InsteonAddress getAddressValue(String value) throws SAXException {
+ Field field = new Field(name, offset, dataType);
try {
- return value.isEmpty() ? InsteonAddress.UNKNOWN : new InsteonAddress(value);
- } catch (IllegalArgumentException e) {
- throw new SAXException("invalid field address value: " + value);
+ field.set(data, element.getTextContent());
+ } catch (FieldException | IllegalArgumentException e) {
+ throw new SAXException("failed to set field data:", e);
}
- }
-
- /**
- * Returns new message template
- *
- * @param fields msg fields
- * @param length msg length
- * @param headerLength header length
- * @param direction msg direction
- * @return new msg template
- * @throws FieldException
- */
- private Msg makeMsgTemplate(Map<Field, Object> fields, int headerLength, int length, Direction direction)
- throws FieldException {
- Msg msg = new Msg(headerLength, length, direction);
- for (Entry<Field, Object> entry : fields.entrySet()) {
- Field field = entry.getKey();
- byte[] data = msg.getData();
- field.set(data, entry.getValue());
- if (!field.getName().isEmpty()) {
- msg.addField(field);
- }
+ if (!name.isEmpty()) {
+ fields.put(name, field);
}
- return msg;
+ return dataType.getSize();
}
/**
logger.trace("got pure nack!");
removeFromBuffer(1);
try {
- msg = Msg.makeMessage("PureNACK");
- return msg;
+ return Msg.makeMessage("PureNACK");
} catch (InvalidMessageTypeException e) {
return null;
}
// If not, we return null, and expect this method to be called again
// when more data has come in.
if (end > 1) {
- // we have some data, but do we have enough to read the entire header?
- int headerLength = Msg.getHeaderLength(buf[1]);
- boolean isExtended = Msg.isExtended(buf, end, headerLength);
- logger.trace("header length expected: {} extended: {}", headerLength, isExtended);
- if (headerLength < 0) {
- if (logger.isDebugEnabled()) {
- logger.debug("got unknown command code: {}", HexUtils.getHexString(buf[1]));
- }
- removeFromBuffer(1); // get rid of the leading 0x02 so draining works
- bail();
- } else if (headerLength >= 2) {
+ try {
+ int headerLength = Msg.getHeaderLength(buf[1]);
+ logger.trace("header length expected: {}", headerLength);
if (end >= headerLength) {
- // only when the header is complete do we know that isExtended is correct!
+ boolean isExtended = Msg.isExtended(buf, headerLength);
int msgLen = Msg.getMessageLength(buf[1], isExtended);
- logger.trace("msgLen expected: {}", msgLen);
- if (msgLen < 0) {
- // Cannot make sense out of the combined command code & isExtended flag.
- if (logger.isDebugEnabled()) {
- logger.debug("got unknown command code/ext flag: {}", HexUtils.getHexString(buf[1]));
- }
- removeFromBuffer(1);
- bail();
- } else if (msgLen > 0) {
- if (end >= msgLen) {
- msg = Msg.createMessage(buf, msgLen, isExtended);
- removeFromBuffer(msgLen);
- }
- } else { // should never happen
- logger.warn("invalid message length, internal error!");
+ logger.trace("msgLen expected: {} extended: {}", msgLen, isExtended);
+ if (end >= msgLen) {
+ msg = Msg.createMessage(buf, msgLen, isExtended);
+ removeFromBuffer(msgLen);
}
}
- } else { // should never happen
- logger.warn("invalid header length, internal error!");
+ } catch (InvalidMessageTypeException e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("got unknown command code: {}", HexUtils.getHexString(buf[1]));
+ }
+ bail();
}
}
// indicate no more messages available in buffer if empty or undefined message
}
private void bail() throws IOException {
- drainBuffer(); // this will drain until end or it finds the next message start
- throw new IOException("bad data received");
- }
-
- private void drainBuffer() {
- while (end > 0 && buf[0] != 0x02) {
+ // drain buffer until end or the next message start
+ do {
removeFromBuffer(1);
- }
+ } while (end > 0 && buf[0] != 0x02);
+ throw new IOException("bad data received");
}
private void removeFromBuffer(int len) {
if (l > end) {
l = end;
}
- System.arraycopy(buf, l, buf, 0, end + 1 - l);
- end -= l;
+ if (l > 0) {
+ System.arraycopy(buf, l, buf, 0, end + 1 - l);
+ end -= l;
+ }
}
}
</header>
<byte name="ALLLinkGroup"/>
<byte name="ALLLinkCommand"/>
- <byte name="ALLLinkCommand2">0x00</byte>
+ <byte name="ALLLinkCommand2"/>
</msg>
<msg name="SendALLLinkCommandReply" length="6" direction="FROM_MODEM">
<header length="2">
</header>
<byte name="command1"/>
<byte name="command2"/>
- <byte name="userData1">0x00</byte>
- <byte name="userData2">0x00</byte>
- <byte name="userData3">0x00</byte>
- <byte name="userData4">0x00</byte>
- <byte name="userData5">0x00</byte>
- <byte name="userData6">0x00</byte>
- <byte name="userData7">0x00</byte>
- <byte name="userData8">0x00</byte>
- <byte name="userData9">0x00</byte>
- <byte name="userData10">0x00</byte>
- <byte name="userData11">0x00</byte>
- <byte name="userData12">0x00</byte>
- <byte name="userData13">0x00</byte>
- <byte name="userData14">0x00</byte>
+ <byte name="userData1"/>
+ <byte name="userData2"/>
+ <byte name="userData3"/>
+ <byte name="userData4"/>
+ <byte name="userData5"/>
+ <byte name="userData6"/>
+ <byte name="userData7"/>
+ <byte name="userData8"/>
+ <byte name="userData9"/>
+ <byte name="userData10"/>
+ <byte name="userData11"/>
+ <byte name="userData12"/>
+ <byte name="userData13"/>
+ <byte name="userData14"/>
</msg>
<msg name="SendExtendedMessageReply" length="23" direction="FROM_MODEM">
<header length="6">
<byte>0x02</byte>
<byte name="Cmd">0x63</byte>
</header>
- <byte name="rawX10"></byte>
- <byte name="X10Flag">0x00</byte>
+ <byte name="rawX10"/>
+ <byte name="X10Flag"/>
</msg>
<msg name="SendX10MessageReply" length="5" direction="FROM_MODEM">
<header length="2">
<byte>0x02</byte>
<byte name="Cmd">0x63</byte>
</header>
- <byte name="rawX10"></byte>
- <byte name="X10Flag">0x00</byte>
+ <byte name="rawX10"/>
+ <byte name="X10Flag"/>
<byte name="ACK/NACK"/>
</msg>
<msg name="StartALLLinking" length="4" direction="TO_MODEM">
<byte>0x02</byte>
<byte name="Cmd">0x64</byte>
</header>
- <byte name="LinkCode"></byte>
- <byte name="ALLLinkGroup">0x00</byte>
+ <byte name="LinkCode"/>
+ <byte name="ALLLinkGroup"/>
</msg>
<msg name="StartALLLinkingReply" length="5" direction="FROM_MODEM">
<header length="2">
<byte>0x02</byte>
<byte name="Cmd">0x64</byte>
</header>
- <byte name="LinkCode"></byte>
- <byte name="ALLLinkGroup">0x00</byte>
+ <byte name="LinkCode"/>
+ <byte name="ALLLinkGroup"/>
<byte name="ACK/NACK"/>
</msg>
<msg name="CancelALLLinking" length="2" direction="TO_MODEM">
<byte name="RecordFlags"/>
<byte name="ALLLinkGroup"/>
<address name="LinkAddr"/>
- <byte name="LinkData1">0x00</byte>
- <byte name="LinkData2">0x00</byte>
- <byte name="LinkData3">0x00</byte>
+ <byte name="LinkData1"/>
+ <byte name="LinkData2"/>
+ <byte name="LinkData3"/>
</msg>
<msg name="WriteDatabaseRecordReply" length="13" direction="FROM_MODEM">
<header length="2">
<byte>0x02</byte>
<byte name="Cmd">0x79</byte>
</header>
- <byte name="LinkData1">0x00</byte>
- <byte name="LinkData2">0x00</byte>
- <byte name="LinkData3">0x00</byte>
+ <byte name="LinkData1"/>
+ <byte name="LinkData2"/>
+ <byte name="LinkData3"/>
</msg>
<msg name="SetNextLinkDataReply" length="6" direction="FROM_MODEM">
<header length="2">