/*
* #%L
* OW2 Chameleon - Fuchsia Framework
* %%
* Copyright (C) 2009 - 2015 OW2 Chameleon
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
/**
* openHAB, the open Home Automation Bus.
* Copyright (C) 2010-2012, openHAB.org <admin@openhab.org>
*
* See the contributors.txt file in the distribution for a
* full listing of individual contributors.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or
* combining it with Eclipse (or a modified version of that library),
* containing parts covered by the terms of the Eclipse Public License
* (EPL), the licensors of this Program grant you additional permission
* to convey the resulting work.
*/
package org.ow2.chameleon.fuchsia.importer.zwave.internal.commandclass;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.ow2.chameleon.fuchsia.importer.zwave.internal.protocol.SerialMessage;
import org.ow2.chameleon.fuchsia.importer.zwave.internal.protocol.ZWaveController;
import org.ow2.chameleon.fuchsia.importer.zwave.internal.protocol.ZWaveEndpoint;
import org.ow2.chameleon.fuchsia.importer.zwave.internal.protocol.ZWaveEvent;
import org.ow2.chameleon.fuchsia.importer.zwave.internal.protocol.ZWaveEvent.ZWaveEventType;
import org.ow2.chameleon.fuchsia.importer.zwave.internal.protocol.ZWaveNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles the Alarm Sensor command class. Alarm sensors indicate an event for each of their
* supported alarm types. The event is reported as occurs (0xFF) or does not occur (0x00).
* The commands include the possibility to get a given value and report a value.
*
* TODO: Add support for more than one sensor type.
*
* @author Jan-Willem Spuij
* @since 1.3.0
*/
public class ZWaveAlarmSensorCommandClass extends ZWaveCommandClass implements ZWaveGetCommands, ZWaveCommandClassInitialization {
private static final Logger logger = LoggerFactory.getLogger(ZWaveAlarmSensorCommandClass.class);
private static final int SENSOR_ALARM_GET = 0x01;
private static final int SENSOR_ALARM_REPORT = 0x02;
private static final int SENSOR_ALARM_SUPPORTED_GET = 0x03;
private static final int SENSOR_ALARM_SUPPORTED_REPORT = 0x04;
private final Map<AlarmType, Integer> alarmValues = new HashMap<AlarmType, Integer>();
/**
* Creates a new instance of the ZWaveAlarmSensorCommandClass class.
* @param node the node this command class belongs to
* @param controller the controller to use
* @param endpoint the endpoint this Command class belongs to
*/
public ZWaveAlarmSensorCommandClass(ZWaveNode node,
ZWaveController controller, ZWaveEndpoint endpoint) {
super(node, controller, endpoint);
}
/**
* {@inheritDoc}
*/
@Override
public CommandClass getCommandClass() {
return CommandClass.SENSOR_ALARM;
}
/**
* {@inheritDoc}
*/
@Override
public void handleApplicationCommandRequest(SerialMessage serialMessage,
int offset, int endpoint) {
logger.trace("Handle Message Sensor Alarm Request");
logger.debug(String.format("Received Sensor Alarm Request for Node ID = %d", this.getNode().getNodeId()));
int command = serialMessage.getMessagePayloadByte(offset);
switch (command) {
case SENSOR_ALARM_GET:
case SENSOR_ALARM_SUPPORTED_GET:
logger.warn(String.format("Command 0x%02X not implemented.", command));
return;
case SENSOR_ALARM_REPORT:
logger.trace("Process Sensor Alarm Report");
int sourceNode = serialMessage.getMessagePayloadByte(offset + 1);
int alarmTypeCode = serialMessage.getMessagePayloadByte(offset + 2);
int value = serialMessage.getMessagePayloadByte(offset + 3);
logger.debug(String.format("Sensor Alarm report from nodeId = %d", this.getNode().getNodeId()));
logger.debug(String.format("Source node ID = %d", sourceNode));
logger.debug(String.format("Value = 0x%02x", value));
AlarmType alarmType = AlarmType.getAlarmType(alarmTypeCode);
if (alarmType == null) {
logger.error(String.format("Unknown Alarm Type = 0x%02x, ignoring report.", alarmTypeCode));
return;
}
logger.debug(String.format("Alarm Type = %s (0x%02x)", alarmType.getLabel(), alarmTypeCode));
this.alarmValues.put(alarmType, value);
for (Integer alarmValue : this.alarmValues.values()) {
value |= alarmValue;
}
Object eventValue;
if (value == 0) {
eventValue = "CLOSED";
} else {
eventValue = "OPEN";
}
ZWaveEvent zEvent = new ZWaveEvent(ZWaveEventType.SENSOR_EVENT, this.getNode().getNodeId(), endpoint, eventValue);
this.getController().notifyEventListeners(zEvent);
break;
case SENSOR_ALARM_SUPPORTED_REPORT:
logger.debug("Process Sensor Supported Alarm Report");
int numBytes = serialMessage.getMessagePayloadByte(offset + 1);
int manufacturerId = this.getNode().getManufacturer();
int deviceType = this.getNode().getDeviceType();
// Fibaro alarm sensors do not provide a bitmap of alarm types, but list them byte by byte.
if (manufacturerId == 0x010F && deviceType == 0x0700) {
logger.warn("Detected Fibaro FGK - 101 Door / Window sensor, activating workaround for incorrect encoding of supported alarm bitmap.");
for(int i=0; i < numBytes; ++i ) {
int index = serialMessage.getMessagePayloadByte(offset + i + 2);
if(index >= AlarmType.values().length)
continue;
AlarmType alarmTypeToAdd = AlarmType.getAlarmType(index);
this.alarmValues.put(alarmTypeToAdd, 0x00);
logger.debug(String.format("Added alarm type %s (0x%02x)", alarmTypeToAdd.getLabel(), index));
}
} else {
for(int i=0; i < numBytes; ++i ) {
for(int bit = 0; bit < 8; ++bit) {
if( ((serialMessage.getMessagePayloadByte(offset + i +2)) & (1 << bit) ) == 0 )
continue;
int index = (i << 3) + bit;
if(index >= AlarmType.values().length)
continue;
// (n)th bit is set. n is the index for the alarm type enumeration.
AlarmType alarmTypeToAdd = AlarmType.getAlarmType(index);
this.alarmValues.put(alarmTypeToAdd, 0x00);
logger.debug(String.format("Added alarm type %s (0x%02x)", alarmTypeToAdd.getLabel(), index));
}
}
}
this.getNode().advanceNodeStage();
break;
default:
logger.warn(String.format("Unsupported Command 0x%02X for command class %s (0x%02X).",
command,
this.getCommandClass().getLabel(),
this.getCommandClass().getKey()));
}
}
/**
* Gets a SerialMessage with the SENSOR_ALARM_GET command
* @return the serial message
*/
public SerialMessage getValueMessage() {
for (AlarmType alarmType : this.alarmValues.keySet()) {
return getMessage(alarmType);
}
// in case there are no supported alarms, get them.
return this.getSupportedMessage();
}
/**
* Gets a SerialMessage with the SENSOR_ALARM_GET command
* @return the serial message
*/
public SerialMessage getMessage(AlarmType alarmType) {
logger.debug("Creating new message for application command SENSOR_ALARM_GET for node {}", this.getNode().getNodeId());
SerialMessage result = new SerialMessage(this.getNode().getNodeId(), SerialMessage.SerialMessageClass.SendData, SerialMessage.SerialMessageType.Request, SerialMessage.SerialMessageClass.ApplicationCommandHandler, SerialMessage.SerialMessagePriority.Get);
byte[] newPayload = { (byte) this.getNode().getNodeId(),
3,
(byte) getCommandClass().getKey(),
(byte) SENSOR_ALARM_GET,
(byte) alarmType.getKey() };
result.setMessagePayload(newPayload);
return result;
}
/**
* Gets a SerialMessage with the SENSOR_ALARM_SUPPORTED_GET command
* @return the serial message, or null if the supported command is not supported.
*/
public SerialMessage getSupportedMessage() {
logger.debug("Creating new message for application command SENSOR_ALARM_SUPPORTED_GET for node {}", this.getNode().getNodeId());
if (this.getNode().getManufacturer() == 0x010F && this.getNode().getDeviceType() == 0x0501) {
logger.warn("Detected Fibaro FGBS001 Universal Sensor - this device fails to respond to SENSOR_ALARM_GET and SENSOR_ALARM_SUPPORTED_GET.");
return null;
}
SerialMessage result = new SerialMessage(this.getNode().getNodeId(), SerialMessage.SerialMessageClass.SendData, SerialMessage.SerialMessageType.Request, SerialMessage.SerialMessageClass.ApplicationCommandHandler, SerialMessage.SerialMessagePriority.Get);
byte[] newPayload = { (byte) this.getNode().getNodeId(),
2,
(byte) getCommandClass().getKey(),
(byte) SENSOR_ALARM_SUPPORTED_GET };
result.setMessagePayload(newPayload);
return result;
}
/**
* Initializes the alarm sensor command class. Requests the supported alarm types.
*/
public Collection<SerialMessage> initialize() {
ArrayList<SerialMessage> result = new ArrayList<SerialMessage>();
if (this.getNode().getManufacturer() == 0x010F && this.getNode().getDeviceType() == 0x0501) {
logger.warn("Detected Fibaro FGBS001 Universal Sensor - this device fails to respond to SENSOR_ALARM_GET and SENSOR_ALARM_SUPPORTED_GET.");
return result;
}
result.add(this.getSupportedMessage());
return result;
}
/**
* Z-Wave AlarmType enumeration. The alarm type indicates the type
* of alarm that is reported.
* @author Jan-Willem Spuij
* @since 1.3.0
*/
public enum AlarmType {
GENERAL(0, "General"),
SMOKE(1, "Smoke"),
CARBON_MONOXIDE(2, "Carbon Monoxide"),
CARBON_DIOXIDE(3, "Carbon Dioxide"),
HEAT(4, "Heat"),
FLOOD(5, "Flood");
/**
* A mapping between the integer code and its corresponding Alarm type
* to facilitate lookup by code.
*/
private static Map<Integer, AlarmType> codeToAlarmTypeMapping;
private int key;
private String label;
private AlarmType(int key, String label) {
this.key = key;
this.label = label;
}
private static void initMapping() {
codeToAlarmTypeMapping = new HashMap<Integer, AlarmType>();
for (AlarmType s : values()) {
codeToAlarmTypeMapping.put(s.key, s);
}
}
/**
* Lookup function based on the alarm type code.
* Returns null if the code does not exist.
* @param i the code to lookup
* @return enumeration value of the alarm type.
*/
public static AlarmType getAlarmType(int i) {
if (codeToAlarmTypeMapping == null) {
initMapping();
}
return codeToAlarmTypeMapping.get(i);
}
/**
* @return the key
*/
public int getKey() {
return key;
}
/**
* @return the label
*/
public String getLabel() {
return label;
}
}
}