/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.zwave.internal.protocol.commandclass; import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; import org.openhab.binding.zwave.internal.config.ZWaveDbCommandClass; import org.openhab.binding.zwave.internal.protocol.SerialMessage; import org.openhab.binding.zwave.internal.protocol.ZWaveController; import org.openhab.binding.zwave.internal.protocol.ZWaveEndpoint; import org.openhab.binding.zwave.internal.protocol.ZWaveNode; import org.openhab.binding.zwave.internal.protocol.commandclass.proprietary.FibaroFGRM222CommandClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamOmitField; /** * Z-Wave Command Class. Z-Wave device functions are controlled * by command classes. A command class can be have one or multiple * commands allowing the use of a certain function of the device. * * @author Brian Crosby * @since 1.3.0 */ public abstract class ZWaveCommandClass { @XStreamOmitField private static final Logger logger = LoggerFactory.getLogger(ZWaveCommandClass.class); private static final int MAX_SUPPORTED_VERSION = 1; private static final int SIZE_MASK = 0x07; // private static final SCALE_MASK = 0x18; // unused // private static final SCALE_SHIFT = 0x03; // unused private static final int PRECISION_MASK = 0xe0; private static final int PRECISION_SHIFT = 0x05; @XStreamOmitField private ZWaveNode node; @XStreamOmitField private ZWaveController controller; @XStreamOmitField private ZWaveEndpoint endpoint; private int version = 0; private int instances = 0; /** * Protected constructor. Initiates a new instance of a Command Class. * * @param node the node this instance commands. * @param controller the controller to send messages to. * @param endpoint the endpoint this Command class belongs to. */ protected ZWaveCommandClass(ZWaveNode node, ZWaveController controller, ZWaveEndpoint endpoint) { this.node = node; this.controller = controller; this.endpoint = endpoint; logger.trace("Command class {} created", getCommandClass().getLabel()); } /** * Returns the node this command class belongs to. * * @return node */ protected ZWaveNode getNode() { return node; } /** * Sets the node this command class belongs to. * * @param node the node to set */ public void setNode(ZWaveNode node) { this.node = node; } /** * Returns the controller to send messages to. * * @return controller */ protected ZWaveController getController() { return controller; } /** * Sets the controller to send messages to. * * @param controller the controller to set */ public void setController(ZWaveController controller) { this.controller = controller; } /** * Returns the endpoint this command class belongs to. * * @return the endpoint this command class belongs to. */ public ZWaveEndpoint getEndpoint() { return endpoint; } /** * Sets the endpoint this command class belongs to. * * @param endpoint the endpoint to set */ public void setEndpoint(ZWaveEndpoint endpoint) { this.endpoint = endpoint; } /** * Returns the version of the command class. * * @return node */ public int getVersion() { return version; } /** * Sets the version number for this command class. * * @param version. The version number to set. */ public void setVersion(int version) { this.version = version; } /** * The maximum version implemented by this command class. */ public int getMaxVersion() { return MAX_SUPPORTED_VERSION; } /** * Set options for this command class. * Options are provided from the device configuration database * * @param options class * @return true if options set ok */ public boolean setOptions(ZWaveDbCommandClass options) { return false; } /** * Returns the number of instances of this command class * in case the node supports the MULTI_INSTANCE command class (Version 1). * * @return the number of instances */ public int getInstances() { return instances; } /** * Returns the number of instances of this command class * in case the node supports the MULTI_INSTANCE command class (Version 1). * * @param instances. The number of instances. */ public void setInstances(int instances) { this.instances = instances; } /** * Returns the command class. * * @return command class */ public abstract CommandClass getCommandClass(); /** * Handles an incoming application command request. * * @param serialMessage the incoming message to process. * @param offset the offset position from which to start message processing. * @param endpoint the endpoint or instance number this message is meant for. */ public abstract void handleApplicationCommandRequest(SerialMessage serialMessage, int offset, int endpoint); /** * Gets an instance of the right command class. * Returns null if the command class is not found. * * @param i the code to instantiate * @param node the node this instance commands. * @param controller the controller to send messages to. * @return the ZWaveCommandClass instance that was instantiated, null otherwise */ public static ZWaveCommandClass getInstance(int i, ZWaveNode node, ZWaveController controller) { return ZWaveCommandClass.getInstance(i, node, controller, null); } /** * Gets an instance of the right command class. * Returns null if the command class is not found. * * @param classId the code to instantiate * @param node the node this instance commands. * @param controller the controller to send messages to. * @param endpoint the endpoint this Command class belongs to * @return the ZWaveCommandClass instance that was instantiated, null otherwise */ public static ZWaveCommandClass getInstance(int classId, ZWaveNode node, ZWaveController controller, ZWaveEndpoint endpoint) { try { CommandClass commandClass = CommandClass.getCommandClass(classId); // Catch the manufacturer specific command class if (commandClass != null && commandClass.equals(CommandClass.MANUFACTURER_PROPRIETARY)) { commandClass = CommandClass.getCommandClass(node.getManufacturer(), node.getDeviceType()); } if (commandClass == null) { logger.debug(String.format("NODE %d: Unknown command class 0x%02x", node.getNodeId(), classId)); return null; } Class<? extends ZWaveCommandClass> commandClassClass = commandClass.getCommandClassClass(); if (commandClassClass == null) { logger.debug("NODE {}: Unsupported command class {}", node.getNodeId(), commandClass.getLabel(), classId); return null; } logger.debug("NODE {}: Creating new instance of command class {}", node.getNodeId(), commandClass.getLabel()); Constructor<? extends ZWaveCommandClass> constructor = commandClassClass.getConstructor(ZWaveNode.class, ZWaveController.class, ZWaveEndpoint.class); return constructor.newInstance(new Object[] { node, controller, endpoint }); } catch (Exception e) { logger.error(String.format("NODE %d: Error instantiating command class 0x%02x", node.getNodeId(), classId), e); return null; } } /** * Extract a decimal value from a byte array. * * @param buffer the buffer to be parsed. * @param offset the offset at which to start reading * @return the extracted decimal value */ protected BigDecimal extractValue(byte[] buffer, int offset) { int size = buffer[offset] & SIZE_MASK; int precision = (buffer[offset] & PRECISION_MASK) >> PRECISION_SHIFT; if ((size + offset) >= buffer.length) { logger.error("Error extracting value - length={}, offset={}, size={}.", new Object[] { buffer.length, offset, size }); throw new NumberFormatException(); } int value = 0; int i; for (i = 0; i < size; ++i) { value <<= 8; value |= buffer[offset + i + 1] & 0xFF; } // Deal with sign extension. All values are signed BigDecimal result; if ((buffer[offset + 1] & 0x80) == 0x80) { // MSB is signed if (size == 1) { value |= 0xffffff00; } else if (size == 2) { value |= 0xffff0000; } } result = BigDecimal.valueOf(value); BigDecimal divisor = BigDecimal.valueOf(Math.pow(10, precision)); return result.divide(divisor); } /** * Extract a decimal value from a byte array. * * @param buffer the buffer to be parsed. * @param offset the offset at which to start reading * @return the extracted decimal value */ protected int extractValue(byte[] buffer, int offset, int size) { int value = 0; for (int i = 0; i < size; ++i) { value <<= 8; value |= buffer[offset + i] & 0xFF; } // Deal with sign extension. All values are signed if ((buffer[offset] & 0x80) == 0x80) { // MSB is signed if (size == 1) { value |= 0xffffff00; } else if (size == 2) { value |= 0xffff0000; } } return value; } /** * Encodes a decimal value as a byte array. * * @param value the decimal value to encode * @param index the value index * @return the value buffer * @throws ArithmeticException when the supplied value is out of range. * @since 1.4.0 */ protected byte[] encodeValue(BigDecimal value) throws ArithmeticException { // Remove any trailing zero's so we send the least amount of bytes possible BigDecimal normalizedValue = value.stripTrailingZeros(); // Make our scale at least 0, precision cannot be more than 7 but // this is guarded by the Integer min / max values already. if (normalizedValue.scale() < 0) { normalizedValue = normalizedValue.setScale(0); } if (normalizedValue.unscaledValue().compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) { throw new ArithmeticException(); } else if (normalizedValue.unscaledValue().compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0) { throw new ArithmeticException(); } // default size = 4 int size = 4; // it might fit in a byte or short if (normalizedValue.unscaledValue().intValue() >= Byte.MIN_VALUE && normalizedValue.unscaledValue().intValue() <= Byte.MAX_VALUE) { size = 1; } else if (normalizedValue.unscaledValue().intValue() >= Short.MIN_VALUE && normalizedValue.unscaledValue().intValue() <= Short.MAX_VALUE) { size = 2; } int precision = normalizedValue.scale(); byte[] result = new byte[size + 1]; // precision + scale (unused) + size result[0] = (byte) ((precision << PRECISION_SHIFT) | size); int unscaledValue = normalizedValue.unscaledValue().intValue(); // ie. 22.5 = 225 for (int i = 0; i < size; i++) { result[size - i] = (byte) ((unscaledValue >> (i * 8)) & 0xFF); } return result; } /** * Command class enumeration. Lists all command classes available. * Unsupported command classes by the binding return null for the command class Class. * Taken from: http://wiki.micasaverde.com/index.php/ZWave_Command_Classes * * @author Jan-Willem Spuij * @since 1.3.0 */ @XStreamAlias("commandClass") public enum CommandClass { NO_OPERATION(0x00, "NO_OPERATION", ZWaveNoOperationCommandClass.class), BASIC(0x20, "BASIC", ZWaveBasicCommandClass.class), CONTROLLER_REPLICATION(0x21, "CONTROLLER_REPLICATION", null), APPLICATION_STATUS(0x22, "APPLICATION_STATUS", ZWaveApplicationStatusClass.class), ZIP_SERVICES(0x23, "ZIP_SERVICES", null), ZIP_SERVER(0x24, "ZIP_SERVER", null), SWITCH_BINARY(0x25, "SWITCH_BINARY", ZWaveBinarySwitchCommandClass.class), SWITCH_MULTILEVEL(0x26, "SWITCH_MULTILEVEL", ZWaveMultiLevelSwitchCommandClass.class), SWITCH_ALL(0x27, "SWITCH_ALL", ZWaveSwitchAllCommandClass.class), SWITCH_TOGGLE_BINARY(0x28, "SWITCH_TOGGLE_BINARY", null), SWITCH_TOGGLE_MULTILEVEL(0x29, "SWITCH_TOGGLE_MULTILEVEL", null), CHIMNEY_FAN(0x2A, "CHIMNEY_FAN", null), SCENE_ACTIVATION(0x2B, "SCENE_ACTIVATION", ZWaveSceneActivationCommandClass.class), SCENE_ACTUATOR_CONF(0x2C, "SCENE_ACTUATOR_CONF", null), SCENE_CONTROLLER_CONF(0x2D, "SCENE_CONTROLLER_CONF", null), ZIP_CLIENT(0x2E, "ZIP_CLIENT", null), ZIP_ADV_SERVICES(0x2F, "ZIP_ADV_SERVICES", null), SENSOR_BINARY(0x30, "SENSOR_BINARY", ZWaveBinarySensorCommandClass.class), SENSOR_MULTILEVEL(0x31, "SENSOR_MULTILEVEL", ZWaveMultiLevelSensorCommandClass.class), METER(0x32, "METER", ZWaveMeterCommandClass.class), ZIP_ADV_SERVER(0x33, "ZIP_ADV_SERVER", null), ZIP_ADV_CLIENT(0x34, "ZIP_ADV_CLIENT", null), METER_PULSE(0x35, "METER_PULSE", null), METER_TBL_CONFIG(0x3C, "METER_TBL_CONFIG", null), METER_TBL_MONITOR(0x3D, "METER_TBL_MONITOR", null), METER_TBL_PUSH(0x3E, "METER_TBL_PUSH", null), THERMOSTAT_HEATING(0x38, "THERMOSTAT_HEATING", null), THERMOSTAT_MODE(0x40, "THERMOSTAT_MODE", ZWaveThermostatModeCommandClass.class), THERMOSTAT_OPERATING_STATE(0x42, "THERMOSTAT_OPERATING_STATE", ZWaveThermostatOperatingStateCommandClass.class), THERMOSTAT_SETPOINT(0x43, "THERMOSTAT_SETPOINT", ZWaveThermostatSetpointCommandClass.class), THERMOSTAT_FAN_MODE(0x44, "THERMOSTAT_FAN_MODE", ZWaveThermostatFanModeCommandClass.class), THERMOSTAT_FAN_STATE(0x45, "THERMOSTAT_FAN_STATE", ZWaveThermostatFanStateCommandClass.class), CLIMATE_CONTROL_SCHEDULE(0x46, "CLIMATE_CONTROL_SCHEDULE", null), THERMOSTAT_SETBACK(0x47, "THERMOSTAT_SETBACK", null), DOOR_LOCK_LOGGING(0x4C, "DOOR_LOCK_LOGGING", null), SCHEDULE_ENTRY_LOCK(0x4E, "SCHEDULE_ENTRY_LOCK", null), BASIC_WINDOW_COVERING(0x50, "BASIC_WINDOW_COVERING", null), MTP_WINDOW_COVERING(0x51, "MTP_WINDOW_COVERING", null), CRC_16_ENCAP(0x56, "CRC_16_ENCAP", ZWaveCRC16EncapsulationCommandClass.class), ASSOCIATION_GROUP_INFO(0x59, "ASSOCIATION_GROUP_INFO", null), DEVICE_RESET_LOCALLY(0x5a, "DEVICE_RESET_LOCALLY", null), CENTRAL_SCENE(0x5b, "CENTRAL_SCENE", ZWaveCentralSceneCommandClass.class), ANTITHEFT(0x5d, "ANTITHEFT", null), ZWAVE_PLUS_INFO(0x5e, "ZWAVE_PLUS_INFO", null), MULTI_INSTANCE(0x60, "MULTI_INSTANCE", ZWaveMultiInstanceCommandClass.class), DOOR_LOCK(0x62, "DOOR_LOCK", ZWaveDoorLockCommandClass.class), USER_CODE(0x63, "USER_CODE", ZWaveUserCodeCommandClass.class), BARRIER_OPERATOR(0x66, "BARRIER_OPERATOR", ZWaveBarrierOperatorCommandClass.class), CONFIGURATION(0x70, "CONFIGURATION", ZWaveConfigurationCommandClass.class), ALARM(0x71, "ALARM", ZWaveAlarmCommandClass.class), MANUFACTURER_SPECIFIC(0x72, "MANUFACTURER_SPECIFIC", ZWaveManufacturerSpecificCommandClass.class), POWERLEVEL(0x73, "POWERLEVEL", null), PROTECTION(0x75, "PROTECTION", ZWaveProtectionCommandClass.class), LOCK(0x76, "LOCK", ZWaveLockCommandClass.class), NODE_NAMING(0x77, "NODE_NAMING", ZWaveNodeNamingCommandClass.class), FIRMWARE_UPDATE_MD(0x7A, "FIRMWARE_UPDATE_MD", null), GROUPING_NAME(0x7B, "GROUPING_NAME", null), REMOTE_ASSOCIATION_ACTIVATE(0x7C, "REMOTE_ASSOCIATION_ACTIVATE", null), REMOTE_ASSOCIATION(0x7D, "REMOTE_ASSOCIATION", null), BATTERY(0x80, "BATTERY", ZWaveBatteryCommandClass.class), CLOCK(0x81, "CLOCK", ZWaveClockCommandClass.class), HAIL(0x82, "HAIL", ZWaveHailCommandClass.class), WAKE_UP(0x84, "WAKE_UP", ZWaveWakeUpCommandClass.class), ASSOCIATION(0x85, "ASSOCIATION", ZWaveAssociationCommandClass.class), VERSION(0x86, "VERSION", ZWaveVersionCommandClass.class), INDICATOR(0x87, "INDICATOR", ZWaveIndicatorCommandClass.class), PROPRIETARY(0x88, "PROPRIETARY", null), LANGUAGE(0x89, "LANGUAGE", null), TIME(0x8A, "TIME", null), TIME_PARAMETERS(0x8B, "TIME_PARAMETERS", null), GEOGRAPHIC_LOCATION(0x8C, "GEOGRAPHIC_LOCATION", null), COMPOSITE(0x8D, "COMPOSITE", null), MULTI_INSTANCE_ASSOCIATION(0x8E, "MULTI_INSTANCE_ASSOCIATION", null), MULTI_CMD(0x8F, "MULTI_CMD", ZWaveMultiCommandCommandClass.class), ENERGY_PRODUCTION(0x90, "ENERGY_PRODUCTION", null), // Note that MANUFACTURER_PROPRIETARY shouldn't be instantiated directly // The getInstance method will catch this and translate to the correct // class for the device. MANUFACTURER_PROPRIETARY(0x91, "MANUFACTURER_PROPRIETARY", null), SCREEN_MD(0x92, "SCREEN_MD", null), SCREEN_ATTRIBUTES(0x93, "SCREEN_ATTRIBUTES", null), SIMPLE_AV_CONTROL(0x94, "SIMPLE_AV_CONTROL", null), AV_CONTENT_DIRECTORY_MD(0x95, "AV_CONTENT_DIRECTORY_MD", null), AV_RENDERER_STATUS(0x96, "AV_RENDERER_STATUS", null), AV_CONTENT_SEARCH_MD(0x97, "AV_CONTENT_SEARCH_MD", null), SECURITY(0x98, "SECURITY", ZWaveSecurityCommandClassWithInitialization.class), AV_TAGGING_MD(0x99, "AV_TAGGING_MD", null), IP_CONFIGURATION(0x9A, "IP_CONFIGURATION", null), ASSOCIATION_COMMAND_CONFIGURATION(0x9B, "ASSOCIATION_COMMAND_CONFIGURATION", null), SENSOR_ALARM(0x9C, "SENSOR_ALARM", ZWaveAlarmSensorCommandClass.class), SILENCE_ALARM(0x9D, "SILENCE_ALARM", null), SENSOR_CONFIGURATION(0x9E, "SENSOR_CONFIGURATION", null), MARK(0xEF, "MARK", null), NON_INTEROPERABLE(0xF0, "NON_INTEROPERABLE", null), // MANUFACTURER_PROPRIETARY class definitions are defined by the manufacturer and device id FIBARO_FGRM_222(0x010F, 0x0301, "FIBARO_FGRM_222", FibaroFGRM222CommandClass.class), FIBARO_FGRM_222_1(0x010F, 0x0302, "FIBARO_FGRM_222", FibaroFGRM222CommandClass.class); /** * A mapping between the integer code and its corresponding * Command class to facilitate lookup by code. */ private static Map<Integer, CommandClass> codeToCommandClassMapping; /** * A mapping between the string label and its corresponding * Command class to facilitate lookup by label. */ private static Map<String, CommandClass> labelToCommandClassMapping; /** * Get unique command class code for manufacturer and device ID. * * To support manufacturer specific implementations of a manufacturer proprietary command class we use the * manufacturer and the device id to generate a unique key. * * @param manufacturer the manufacturer ID * @param deviceId the device ID * @return a unique command class key */ private static int getKeyFromManufacturerAndDeviceId(int manufacturer, int deviceId) { return manufacturer << 16 | deviceId; } private int key; private String label; private Class<? extends ZWaveCommandClass> commandClassClass; private CommandClass(int key, String label, Class<? extends ZWaveCommandClass> commandClassClass) { this.key = key; this.label = label; this.commandClassClass = commandClassClass; } private CommandClass(int manufacturer, int deviceId, String label, Class<? extends ZWaveCommandClass> commandClassClass) { this(getKeyFromManufacturerAndDeviceId(manufacturer, deviceId), label, commandClassClass); } private static void initMapping() { codeToCommandClassMapping = new HashMap<Integer, CommandClass>(); labelToCommandClassMapping = new HashMap<String, CommandClass>(); for (CommandClass s : values()) { codeToCommandClassMapping.put(s.key, s); labelToCommandClassMapping.put(s.label.toLowerCase(), s); } } /** * Lookup function based on the command class code. * Returns null if there is no command class with code i * * @param i the code to lookup * @return enumeration value of the command class. */ public static CommandClass getCommandClass(int i) { if (codeToCommandClassMapping == null) { initMapping(); } CommandClass commandClass = codeToCommandClassMapping.get(i); if (commandClass == null) { logger.debug(String.format("CommandClass not found for 0x%02X", i)); } return commandClass; } /** * Lookup function based on the manufacturer and device ID. * * @param manufacturer the manufacturer ID * @param deviceId the device ID * @return enumeration value of the command class or null if there is no command class. */ public static CommandClass getCommandClass(int manufacturer, int deviceId) { return getCommandClass(getKeyFromManufacturerAndDeviceId(manufacturer, deviceId)); } /** * Lookup function based on the command class label. * Returns null if there is no command class with that label. * * @param label the label to lookup * @return enumeration value of the command class. */ public static CommandClass getCommandClass(String label) { if (labelToCommandClassMapping == null) { initMapping(); } return labelToCommandClassMapping.get(label.toLowerCase()); } /** * @return the key */ public int getKey() { return key; } /** * @return the label */ public String getLabel() { return label; } /** * @return the command class Class */ public Class<? extends ZWaveCommandClass> getCommandClassClass() { return commandClassClass; } } }