/** * * Copyright (c) 2009-2016 Freedomotic team http://freedomotic.com * * This file is part of Freedomotic * * 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 2, 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 * Freedomotic; see the file COPYING. If not, see * <http://www.gnu.org/licenses/>. */ package com.freedomotic.plugins.devices.zwave; import com.freedomotic.api.EventTemplate; import com.freedomotic.api.Protocol; import com.freedomotic.events.ProtocolRead; import com.freedomotic.exceptions.UnableToExecuteException; import com.freedomotic.reactions.Command; import java.io.File; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zwave4j.ControllerCallback; import org.zwave4j.ControllerCommand; import org.zwave4j.ControllerError; import org.zwave4j.ControllerState; import org.zwave4j.Manager; import org.zwave4j.NativeLibraryLoader; import org.zwave4j.Notification; import org.zwave4j.NotificationWatcher; import org.zwave4j.Options; import org.zwave4j.ValueGenre; import org.zwave4j.ValueId; import org.zwave4j.ValueType; import org.zwave4j.ZWave4j; /** * * @author Mauro Cicolella */ public class Zwave4FD extends Protocol { private static final Logger LOG = LoggerFactory.getLogger(Zwave4FD.class.getName()); final int POLLING_WAIT; private Manager manager; private NotificationWatcher watcher; private String controllerPort; private Options options; private long homeId; private boolean ready; private static ControllerCallback GENERIC_COMMAND_CALLBACK = new ControllerCallback() { @Override public void onCallback(ControllerState cs, ControllerError ce, Object o) { if (cs.equals(ControllerState.COMPLETED)) { LOG.info("Successfully complete operation"); } else if (cs.equals(ControllerState.CANCEL)) { LOG.info("Operation canceled"); } } }; /** * */ public Zwave4FD() { super("Zwave4FD", "/zwave/zwave-manifest.xml"); POLLING_WAIT = configuration.getIntProperty("time-between-reads", -1); setPollingWait(-1); //millisecs interval between hardware device status reads NativeLibraryLoader.loadLibrary(ZWave4j.LIBRARY_NAME, ZWave4j.class); } private Object getValue(ValueId valueId) { switch (valueId.getType()) { case BOOL: AtomicReference<Boolean> b = new AtomicReference<Boolean>(); Manager.get().getValueAsBool(valueId, b); return b.get(); case BYTE: AtomicReference<Short> bb = new AtomicReference<Short>(); Manager.get().getValueAsByte(valueId, bb); return bb.get(); case DECIMAL: AtomicReference<Float> f = new AtomicReference<Float>(); Manager.get().getValueAsFloat(valueId, f); return f.get(); case INT: AtomicReference<Integer> i = new AtomicReference<Integer>(); Manager.get().getValueAsInt(valueId, i); return i.get(); case LIST: return null; case SCHEDULE: return null; case SHORT: AtomicReference<Short> s = new AtomicReference<Short>(); Manager.get().getValueAsShort(valueId, s); return s.get(); case STRING: AtomicReference<String> ss = new AtomicReference<String>(); Manager.get().getValueAsString(valueId, ss); return ss.get(); case BUTTON: return null; case RAW: AtomicReference<short[]> sss = new AtomicReference<short[]>(); Manager.get().getValueAsRaw(valueId, sss); return sss.get(); default: return null; } } @Override protected void onShowGui() { } @Override protected void onHideGui() { //implement here what to do when the this plugin GUI is closed //for example you can change the plugin description setDescription("My GUI is now hidden"); } @Override protected void onRun() { } @Override protected void onStart() { LOG.info("Zwave plugin started"); controllerPort = configuration.getStringProperty("zw-controller-port", "/dev/ttyS0"); options = Options.create(new File(getFile().getParentFile() + "/data/config").getAbsolutePath(), "", ""); options.addOptionBool("ConsoleOutput", configuration.getBooleanProperty("zw-console-output", false)); options.addOptionInt("DriverMaxAttempts", configuration.getIntProperty("zw-driver-max-attempts", 1)); options.addOptionBool("SaveConfiguration", configuration.getBooleanProperty("zw-save-configuration", false)); options.addOptionBool("Logging", configuration.getBooleanProperty("zw-logging", false)); options.addOptionInt("SaveLogLevel", configuration.getIntProperty("zw-save-log-level", 6)); // more options could be picked up from https://code.google.com/p/open-zwave/wiki/Config_Options options.lock(); watcher = new NotificationWatcher() { @Override public void onNotification(Notification notification, Object obj) { switch (notification.getType()) { case DRIVER_READY: homeId = notification.getHomeId(); setDescription("Zwave is using device '" + controllerPort + "'"); LOG.info(String.format("Driver ready - home id: %d", homeId)); break; case DRIVER_FAILED: LOG.warn("Driver failed - controller port was '{}'", controllerPort); stop(); break; case DRIVER_RESET: LOG.info("Driver reset"); break; case AWAKE_NODES_QUERIED: LOG.info("Awake nodes queried"); break; case ALL_NODES_QUERIED: LOG.info("All nodes queried"); manager.writeConfig(homeId); ready = true; break; case ALL_NODES_QUERIED_SOME_DEAD: LOG.warn("All nodes queried some dead"); manager.writeConfig(homeId); ready = true; break; case POLLING_ENABLED: LOG.info("Polling enabled"); break; case POLLING_DISABLED: LOG.info("Polling disabled"); break; case NODE_NEW: LOG.info(String.format("Node new - id: %d", notification.getNodeId())); break; case NODE_ADDED: LOG.info(String.format("Node added - id: %d, name: %s, manufacturer: %s, product: %s, type: %s", notification.getNodeId(), manager.getNodeName(homeId, notification.getNodeId()), manager.getNodeManufacturerName(homeId, notification.getNodeId()), manager.getNodeProductName(homeId, notification.getNodeId()), manager.getNodeProductType(homeId, notification.getNodeId()))); break; case NODE_REMOVED: LOG.info(String.format("Node removed -id: %d", notification.getNodeId())); break; case ESSENTIAL_NODE_QUERIES_COMPLETE: LOG.info(String.format("Node essential queries complete - id: %d", notification.getNodeId())); break; case NODE_QUERIES_COMPLETE: LOG.info(String.format("Node queries complete - id: %d", notification.getNodeId())); break; case NODE_EVENT: LOG.info(String.format("Node event {node: %d, event: %d}", notification.getNodeId(), notification.getEvent())); break; case NODE_NAMING: LOG.info(String.format("Node naming - id: %d", notification.getNodeId())); break; case NODE_PROTOCOL_INFO: LOG.info(String.format("Node protocol info {node: %d, type: %s}", notification.getNodeId(), manager.getNodeType(notification.getHomeId(), notification.getNodeId()))); break; case VALUE_ADDED: LOG.info(String.format("Value added {node: %d, command class: %d, instance: %d, index: %d, genre: %s, type: %s, label: %s, value: %s}", notification.getValueId().getNodeId(), notification.getValueId().getCommandClassId(), notification.getValueId().getInstance(), notification.getValueId().getIndex(), notification.getValueId().getGenre().name(), notification.getValueId().getType().name(), manager.getValueLabel(notification.getValueId()), getValue(notification.getValueId()))); sendNotification(notification.getValueId()); break; case VALUE_REMOVED: LOG.info(String.format("Value removed {node: %d, command class: %d, instance: %d,index: %d}", notification.getNodeId(), notification.getValueId().getCommandClassId(), notification.getValueId().getInstance(), notification.getValueId().getIndex())); break; case VALUE_CHANGED: LOG.info(String.format("Value changed {node: %d, command class: %d, instance: %d, index: %d, value: %s}", notification.getNodeId(), notification.getValueId().getCommandClassId(), notification.getValueId().getInstance(), notification.getValueId().getIndex(), getValue(notification.getValueId()))); sendNotification(notification.getValueId()); break; case VALUE_REFRESHED: LOG.info(String.format("Value refreshed {node: %d, command class: %d,instance: %d, index: %d, value: %s}", notification.getNodeId(), notification.getValueId().getCommandClassId(), notification.getValueId().getInstance(), notification.getValueId().getIndex(), getValue(notification.getValueId()))); break; case GROUP: LOG.info(String.format("Group { node id: %d, group id: %d }", notification.getNodeId(), notification.getGroupIdx())); break; case SCENE_EVENT: LOG.info(String.format("Scene event - id: %d", notification.getSceneId())); break; case CREATE_BUTTON: LOG.info(String.format("Button create - id: %d", notification.getButtonId())); break; case DELETE_BUTTON: LOG.info(String.format("Button delete - id: %d", notification.getButtonId())); break; case BUTTON_ON: LOG.info(String.format("Button on - id: %d", notification.getButtonId())); break; case BUTTON_OFF: LOG.info(String.format("Button off - id: %d", notification.getButtonId())); break; case NOTIFICATION: LOG.info("Notification"); break; default: LOG.info(notification.getType().name()); break; } } }; manager = Manager.create(); manager.addWatcher(watcher, null); manager.addDriver(controllerPort); } @Override protected void onStop() { manager.removeWatcher(watcher, null); manager.removeDriver(controllerPort); Manager.destroy(); Options.destroy(); LOG.info("Zwave plugin stopped "); } @Override protected void onCommand(Command c) throws IOException, UnableToExecuteException { LOG.info("Zwave plugin receives a command called {} with parameters {}", new Object[]{c.getName(), c.getProperties().toString()}); String commandName = c.getProperty("command"); /* * if (commandName.equalsIgnoreCase("SET-VALUE")) { String[] address = * c.getProperty("address").split(":"); short nodeId = * Short.parseShort(address[0]); short commandClassId = * Short.parseShort(address[1]); short instance = * Short.parseShort(address[2]); * * ValueId vID = new ValueId(homeId, nodeId, ValueGenre.USER, * commandClassId, instance, (short) 1, ValueType.DECIMAL); * manager.setValueAsString(vID, * c.getProperty("owner.object.behavior.temperature")); * * } else if (commandName.equalsIgnoreCase("SWITCH")) { String[] address * = c.getProperty("address").split(":"); short nodeId = * Short.parseShort(address[0]); short instance = * Short.parseShort(address[2]); * * ValueId vID = new ValueId(homeId, nodeId, ValueGenre.USER, (short) * 37, instance, (short) 0, ValueType.BOOL); * manager.setValueAsString(vID, * c.getProperty("owner.object.behavior.powered")); * * } * else if (commandName.equalsIgnoreCase("TOGGLE")) { String[] address = * c.getProperty("address").split(":"); short nodeId = * Short.parseShort(address[0]); short instance = * Short.parseShort(address[2]); * * ValueId vID = new ValueId(homeId, nodeId, ValueGenre.USER, (short) * 40, instance, (short) 0, ValueType.BOOL); * manager.setValueAsString(vID, * c.getProperty("owner.object.behavior.powered")); * * } else */ if (commandName != null && commandName.equalsIgnoreCase("INCLUDE-DEVICE")) { // code to let a Zwave device associate to the master manager.cancelControllerCommand(homeId); LOG.info("Started accepting device inclusion request"); manager.beginControllerCommand(homeId, ControllerCommand.ADD_DEVICE, GENERIC_COMMAND_CALLBACK); TimerTask tt = new TimerTask() { @Override public void run() { manager.cancelControllerCommand(homeId); } }; new Timer().schedule(tt, Long.parseLong(c.getProperty("timeout"))); } else if (commandName != null && commandName.equalsIgnoreCase("EXCLUDE-DEVICE")) { // code to let or foce a device disconnect from master manager.cancelControllerCommand(homeId); LOG.info("Started accepting device exclusion request"); manager.beginControllerCommand(homeId, ControllerCommand.REMOVE_DEVICE, GENERIC_COMMAND_CALLBACK); TimerTask tt = new TimerTask() { @Override public void run() { manager.cancelControllerCommand(homeId); } }; new Timer().schedule(tt, Long.parseLong(c.getProperty("timeout"))); } else { // generic control command String[] address = c.getProperty("address").split(":"); short nodeId = Short.parseShort(address[0]); if (c.getProperty("zwave.nodeId") != null && !c.getProperty("zwave.nodeIdclass").isEmpty()) { nodeId = Short.parseShort(c.getProperty("zwave.nodeId")); } short commandClassId = Short.parseShort(address[1]); if (c.getProperty("zwave.class") != null && !c.getProperty("zwave.class").isEmpty()) { commandClassId = Short.parseShort(c.getProperty("zwave.class")); } short instance = Short.parseShort(address[2]); if (c.getProperty("zwave.instance") != null && !c.getProperty("zwave.instance").isEmpty()) { instance = Short.parseShort(c.getProperty("zwave.instance")); } short index = 0; if (c.getProperty("zwave.index") != null && !c.getProperty("zwave.index").isEmpty()) { index = Short.parseShort(c.getProperty("zwave.index")); } String valueType = "STRING"; if (c.getProperty("zwave.valueType") != null && !c.getProperty("zwave.valueType").isEmpty()) { valueType = c.getProperty("zwave.valueType"); } ValueId vID = new ValueId(homeId, nodeId, ValueGenre.USER, commandClassId, instance, index, ValueType.valueOf(valueType)); manager.setValueAsString(vID, c.getProperty("zwave.value")); } } @Override protected boolean canExecute(Command c) { //don't mind this method for now throw new UnsupportedOperationException("Not supported yet."); } @Override protected void onEvent(EventTemplate event) { //don't mind this method for now throw new UnsupportedOperationException("Not supported yet."); } /* * Notifies event to Freedomotic * * @param ValueId * */ private void sendNotification(ValueId id) { // object address format nodeID:commadClass:instance:index String address = id.getNodeId() + ":" + id.getCommandClassId() + ":" + id.getInstance() + ":" + id.getIndex(); ProtocolRead event = new ProtocolRead(this, "zwave", address); event.addProperty("inputValue", getValue(id) != null ? getValue(id).toString() : "0"); event.addProperty("zwave.index", new Short(id.getIndex()).toString()); event.addProperty("value.label", manager.getValueLabel(id)); event.addProperty("value.unit", manager.getValueUnits(id)); event.addProperty("zwave.command", new Short(id.getCommandClassId()).toString()); if (configuration.getBooleanProperty("auto-configuration", true)) { // for sensorMultilevel the object is mapped to the value type (temperature, luminescence, relative humidity etc) if (id.getCommandClassId() == 49) { if ((configuration.getStringProperty(manager.getValueLabel(id), null) != null)) { event.addProperty("object.class", configuration.getStringProperty(manager.getValueLabel(id), null)); } // otherwise to command class } else { if ((configuration.getStringProperty(new Short(id.getCommandClassId()).toString(), null) != null)) { event.addProperty("object.class", configuration.getStringProperty(new Short(id.getCommandClassId()).toString(), null)); } } String devName = manager.getNodeManufacturerName(homeId, id.getNodeId()) + " - " + manager.getNodeProductName(homeId, id.getNodeId()); event.addProperty("object.name", devName); } this.notifyEvent(event); } }