mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[insteon] Refactor msg definition/factory and product data classes (#17537)
Signed-off-by: jsetton <jeremy.setton@gmail.com>
This commit is contained in:
parent
834b47c700
commit
7a5ba4e9b0
@ -33,10 +33,10 @@ import org.openhab.binding.insteon.internal.device.InsteonScene;
|
||||
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;
|
||||
|
@ -245,7 +245,7 @@ public class InsteonModem extends BaseDevice<InsteonAddress, InsteonBridgeHandle
|
||||
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();
|
||||
|
@ -87,9 +87,9 @@ public class InsteonScene implements Scene {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -354,6 +354,14 @@ public class InsteonScene implements Scene {
|
||||
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;
|
||||
}
|
||||
|
@ -231,21 +231,9 @@ public class ProductData {
|
||||
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;
|
||||
}
|
||||
|
@ -45,9 +45,9 @@ public class ProductDataRegistry extends InsteonResourceLoader {
|
||||
*
|
||||
* @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",
|
||||
@ -55,19 +55,7 @@ public class ProductDataRegistry extends InsteonResourceLoader {
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,7 +131,9 @@ public class ProductDataRegistry extends InsteonResourceLoader {
|
||||
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);
|
||||
|
||||
|
@ -17,6 +17,7 @@ import static org.openhab.binding.insteon.internal.InsteonBindingConstants.*;
|
||||
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;
|
||||
@ -64,6 +65,8 @@ public enum RampRate {
|
||||
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()));
|
||||
|
||||
@ -105,7 +108,7 @@ public enum RampRate {
|
||||
* @return true if supported
|
||||
*/
|
||||
public static boolean supportsFeatureType(String featureType) {
|
||||
return FEATURE_TYPE_GENERIC_DIMMER.equals(featureType);
|
||||
return SUPPORTED_FEATURE_TYPES.contains(featureType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,6 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
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;
|
||||
@ -290,7 +289,7 @@ public class ModemDBReader implements PortListener {
|
||||
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
|
||||
|
@ -62,8 +62,7 @@ public class InsteonDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
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);
|
||||
@ -72,8 +71,7 @@ public class InsteonDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
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);
|
||||
|
@ -284,14 +284,14 @@ public class InsteonBridgeHandler extends InsteonBaseThingHandler implements Bri
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import java.util.function.Function;
|
||||
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.
|
||||
@ -29,8 +30,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@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()));
|
||||
@ -55,9 +55,9 @@ public enum DataType {
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,12 @@ public final class Field {
|
||||
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;
|
||||
}
|
||||
@ -42,16 +48,6 @@ public final class Field {
|
||||
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");
|
||||
@ -61,16 +57,16 @@ public final class Field {
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,8 +137,6 @@ public final class Field {
|
||||
case ADDRESS:
|
||||
s += getAddress(data).toString();
|
||||
break;
|
||||
default:
|
||||
throw new FieldException("field data type unknown");
|
||||
}
|
||||
} catch (FieldException e) {
|
||||
s += "NULL";
|
||||
|
@ -38,38 +38,21 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@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(byte[] data, MsgDefinition definition) {
|
||||
this.data = Arrays.copyOf(data, definition.getLength());
|
||||
this.definition = definition;
|
||||
}
|
||||
|
||||
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(Msg msg) {
|
||||
this(msg, msg.data, msg.data.length);
|
||||
public Msg(MsgDefinition definition) {
|
||||
this(definition.getData(), definition);
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
@ -81,15 +64,11 @@ public class Msg {
|
||||
}
|
||||
|
||||
public int getHeaderLength() {
|
||||
return headerLength;
|
||||
return definition.getHeaderLength();
|
||||
}
|
||||
|
||||
public Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
public MsgDefinition getDefinition() {
|
||||
return definition;
|
||||
return definition.getDirection();
|
||||
}
|
||||
|
||||
public long getQuietTime() {
|
||||
@ -129,11 +108,11 @@ public class Msg {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -244,10 +223,6 @@ public class Msg {
|
||||
return replayed;
|
||||
}
|
||||
|
||||
public void setDefinition(MsgDefinition definition) {
|
||||
this.definition = definition;
|
||||
}
|
||||
|
||||
public void setQuietTime(long quietTime) {
|
||||
this.quietTime = quietTime;
|
||||
}
|
||||
@ -256,10 +231,6 @@ public class Msg {
|
||||
this.replayed = replayed;
|
||||
}
|
||||
|
||||
public void addField(Field f) {
|
||||
definition.addField(f);
|
||||
}
|
||||
|
||||
public boolean containsField(String key) {
|
||||
return definition.containsField(key);
|
||||
}
|
||||
@ -283,12 +254,11 @@ public class Msg {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -443,27 +413,6 @@ public class Msg {
|
||||
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
|
||||
*
|
||||
@ -644,7 +593,7 @@ public class Msg {
|
||||
|
||||
@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() + "|";
|
||||
@ -668,8 +617,8 @@ public class Msg {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -678,10 +627,12 @@ public class Msg {
|
||||
*
|
||||
* @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"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -690,32 +641,27 @@ public class Msg {
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -726,7 +672,7 @@ public class Msg {
|
||||
* @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)));
|
||||
}
|
||||
@ -739,7 +685,7 @@ public class Msg {
|
||||
* @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));
|
||||
}
|
||||
|
||||
|
@ -12,12 +12,11 @@
|
||||
*/
|
||||
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.
|
||||
@ -30,34 +29,34 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
*/
|
||||
@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) {
|
||||
@ -65,4 +64,41 @@ public class MsgDefinition {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -14,14 +14,10 @@ package org.openhab.binding.insteon.internal.transport.message;
|
||||
|
||||
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;
|
||||
@ -39,44 +35,46 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,7 +82,7 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
|
||||
*
|
||||
* @return currently known message definitions
|
||||
*/
|
||||
public Map<String, Msg> getDefinitions() {
|
||||
public Map<String, MsgDefinition> getDefinitions() {
|
||||
return definitions;
|
||||
}
|
||||
|
||||
@ -131,12 +129,22 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
|
||||
* @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++) {
|
||||
@ -144,27 +152,26 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,12 +179,16 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
|
||||
* 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();
|
||||
@ -186,10 +197,10 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
|
||||
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;
|
||||
@ -200,95 +211,29 @@ public class MsgDefinitionRegistry extends InsteonResourceLoader {
|
||||
*
|
||||
* @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);
|
||||
if (dataType == null) {
|
||||
throw new SAXException("invalid field data type");
|
||||
}
|
||||
Field field = new Field(name, offset, dataType);
|
||||
try {
|
||||
field.set(data, element.getTextContent());
|
||||
} catch (FieldException | IllegalArgumentException e) {
|
||||
throw new SAXException("failed to set field data:", e);
|
||||
}
|
||||
if (!name.isEmpty()) {
|
||||
fields.put(name, field);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns field value as an insteon address
|
||||
*
|
||||
* @param value value to convert
|
||||
* @return insteon address
|
||||
* @throws SAXException
|
||||
*/
|
||||
private InsteonAddress getAddressValue(String value) throws SAXException {
|
||||
try {
|
||||
return value.isEmpty() ? InsteonAddress.UNKNOWN : new InsteonAddress(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SAXException("invalid field address value: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance function
|
||||
*
|
||||
|
@ -97,8 +97,7 @@ public class MsgFactory {
|
||||
logger.trace("got pure nack!");
|
||||
removeFromBuffer(1);
|
||||
try {
|
||||
msg = Msg.makeMessage("PureNACK");
|
||||
return msg;
|
||||
return Msg.makeMessage("PureNACK");
|
||||
} catch (InvalidMessageTypeException e) {
|
||||
return null;
|
||||
}
|
||||
@ -112,39 +111,23 @@ public class MsgFactory {
|
||||
// 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) {
|
||||
try {
|
||||
int headerLength = Msg.getHeaderLength(buf[1]);
|
||||
logger.trace("header length expected: {}", headerLength);
|
||||
if (end >= headerLength) {
|
||||
boolean isExtended = Msg.isExtended(buf, headerLength);
|
||||
int msgLen = Msg.getMessageLength(buf[1], isExtended);
|
||||
logger.trace("msgLen expected: {} extended: {}", msgLen, isExtended);
|
||||
if (end >= msgLen) {
|
||||
msg = Msg.createMessage(buf, msgLen, isExtended);
|
||||
removeFromBuffer(msgLen);
|
||||
}
|
||||
}
|
||||
} catch (InvalidMessageTypeException e) {
|
||||
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) {
|
||||
if (end >= headerLength) {
|
||||
// only when the header is complete do we know that isExtended is correct!
|
||||
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!");
|
||||
}
|
||||
}
|
||||
} else { // should never happen
|
||||
logger.warn("invalid header length, internal error!");
|
||||
}
|
||||
}
|
||||
// indicate no more messages available in buffer if empty or undefined message
|
||||
@ -159,14 +142,11 @@ public class MsgFactory {
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -174,7 +154,9 @@ public class MsgFactory {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +163,7 @@
|
||||
</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">
|
||||
@ -205,20 +205,20 @@
|
||||
</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">
|
||||
@ -250,16 +250,16 @@
|
||||
<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">
|
||||
@ -267,16 +267,16 @@
|
||||
<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">
|
||||
@ -550,9 +550,9 @@
|
||||
<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">
|
||||
@ -603,9 +603,9 @@
|
||||
<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">
|
||||
|
Loading…
Reference in New Issue
Block a user