/*
* Copyright 2012-2014 Nikolay A. Viguro
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
*/
package ru.iris.devices.zwave;
import com.avaje.ebean.Ebean;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.zwave4j.*;
import ru.iris.common.Config;
import ru.iris.common.Utils;
import ru.iris.common.database.model.SensorData;
import ru.iris.common.database.model.devices.Device;
import ru.iris.common.database.model.devices.DeviceValue;
import ru.iris.common.helpers.DBLogger;
import ru.iris.common.messaging.JsonEnvelope;
import ru.iris.common.messaging.JsonMessaging;
import ru.iris.common.messaging.JsonNotification;
import ru.iris.common.messaging.model.devices.GenericAdvertisement;
import ru.iris.common.modulestatus.Status;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class ZWaveService {
private final Logger LOGGER = LogManager.getLogger(ZWaveService.class.getName());
private final Gson gson = new GsonBuilder().create();
private long homeId;
private boolean ready = false;
private JsonMessaging messaging;
private Manager manager;
private NotificationWatcher watcher;
private final Config config = Config.getInstance();
public ZWaveService() {
Status status = new Status("ZWave");
if (status.checkExist()) {
status.running();
} else {
status.addIntoDB("ZWave", "Service that comminicate with ZWave devices");
}
try {
messaging = new JsonMessaging(UUID.randomUUID(), "devices-zwave");
NativeLibraryLoader.loadLibrary(ZWave4j.LIBRARY_NAME, ZWave4j.class);
final Options options = Options.create(config.get("openzwaveCfgPath"), "", "");
options.addOptionBool("ConsoleOutput", Boolean.parseBoolean(config.get("zwaveDebug")));
options.addOptionString("UserPath", "conf/", true);
options.lock();
manager = Manager.create();
watcher = new NotificationWatcher() {
@Override
public void onNotification(Notification notification, Object context) {
short node = notification.getNodeId();
Device device = null;
Map<String, Object> data = new HashMap<>();
switch (notification.getType()) {
case DRIVER_READY:
homeId = notification.getHomeId();
LOGGER.info("Driver ready. Home ID: " + homeId);
messaging.broadcast("event.devices.zwave.driver.ready", new GenericAdvertisement("ZWaveDriverReady", homeId));
break;
case DRIVER_FAILED:
LOGGER.info("Driver failed");
messaging.broadcast("event.devices.zwave.driver.failed", new GenericAdvertisement("ZWaveDriverFailed"));
break;
case DRIVER_RESET:
LOGGER.info("Driver reset");
messaging.broadcast("event.devices.zwave.driver.reset", new GenericAdvertisement("ZWaveDriverReset"));
break;
case AWAKE_NODES_QUERIED:
LOGGER.info("Awake nodes queried");
ready = true;
messaging.broadcast("event.devices.zwave.awakenodesqueried", new GenericAdvertisement("ZWaveAwakeNodesQueried"));
break;
case ALL_NODES_QUERIED:
LOGGER.info("All node queried");
manager.writeConfig(homeId);
ready = true;
messaging.broadcast("event.devices.zwave.allnodesqueried", new GenericAdvertisement("ZWaveAllNodesQueried"));
break;
case ALL_NODES_QUERIED_SOME_DEAD:
LOGGER.info("All node queried, some dead");
manager.writeConfig(homeId);
messaging.broadcast("event.devices.zwave.allnodesqueriedsomedead", new GenericAdvertisement("ZWaveAllNodesQueriedSomeDead"));
break;
case POLLING_ENABLED:
LOGGER.info("Polling enabled");
messaging.broadcast("event.devices.zwave.polling.enabled", new GenericAdvertisement("ZWavePollingEnabled", notification.getNodeId()));
break;
case POLLING_DISABLED:
LOGGER.info("Polling disabled");
messaging.broadcast("event.devices.zwave.polling.disabled", new GenericAdvertisement("ZWavePollingDisabled", notification.getNodeId()));
break;
case NODE_NEW:
messaging.broadcast("event.devices.zwave.node.new", new GenericAdvertisement("ZWaveNodeNew", notification.getNodeId()));
break;
case NODE_ADDED:
messaging.broadcast("event.devices.zwave.node.added", new GenericAdvertisement("ZWaveNodeAdded", notification.getNodeId()));
break;
case NODE_REMOVED:
messaging.broadcast("event.devices.zwave.node.removed", new GenericAdvertisement("ZWaveNodeRemoved", notification.getNodeId()));
break;
case ESSENTIAL_NODE_QUERIES_COMPLETE:
messaging.broadcast("event.devices.zwave.essentialnodequeriscomplete", new GenericAdvertisement("ZWaveEssentialsNodeQueriesComplete"));
break;
case NODE_QUERIES_COMPLETE:
messaging.broadcast("event.devices.zwave.queriescomplete", new GenericAdvertisement("ZWaveNodeQueriesComplete"));
break;
case NODE_EVENT:
LOGGER.info("Update info for node " + node);
manager.refreshNodeInfo(homeId, node);
messaging.broadcast("event.devices.zwave.node.event", new GenericAdvertisement("ZWaveNodeEvent", notification.getNodeId()));
break;
case NODE_NAMING:
messaging.broadcast("event.devices.zwave.node.naming", new GenericAdvertisement("ZWaveNodeNaming", notification.getNodeId()));
break;
case NODE_PROTOCOL_INFO:
messaging.broadcast("event.devices.zwave.node.protocolinfo", new GenericAdvertisement("ZWaveNodeProtocolInfo", notification.getNodeId()));
break;
case VALUE_ADDED:
// check empty label
if (Manager.get().getValueLabel(notification.getValueId()).isEmpty())
break;
String nodeType = manager.getNodeType(homeId, node);
switch (nodeType) {
case "Portable Remote Controller":
device = addZWaveDeviceOrValue("controller", notification);
break;
//////////////////////////////////
case "Multilevel Power Switch":
device = addZWaveDeviceOrValue("dimmer", notification);
break;
//////////////////////////////////
case "Routing Alarm Sensor":
device = addZWaveDeviceOrValue("alarmsensor", notification);
break;
case "Binary Power Switch":
device = addZWaveDeviceOrValue("switch", notification);
break;
case "Routing Binary Sensor":
device = addZWaveDeviceOrValue("binarysensor", notification);
break;
//////////////////////////////////
case "Routing Multilevel Sensor":
device = addZWaveDeviceOrValue("multilevelsensor", notification);
break;
//////////////////////////////////
case "Simple Meter":
device = addZWaveDeviceOrValue("metersensor", notification);
break;
//////////////////////////////////
case "Simple Window Covering":
device = addZWaveDeviceOrValue("drapes", notification);
break;
//////////////////////////////////
case "Setpoint Thermostat":
device = addZWaveDeviceOrValue("thermostat", notification);
break;
//////////////////////////////////
//////////////////////////////////
default:
LOGGER.info("Unassigned value for node" +
node +
" type " +
manager.getNodeType(notification.getHomeId(), notification.getNodeId()) +
" class " +
notification.getValueId().getCommandClassId() +
" genre " +
notification.getValueId().getGenre() +
" label " +
manager.getValueLabel(notification.getValueId()) +
" value " +
Utils.getValue(notification.getValueId()) +
" index " +
notification.getValueId().getIndex() +
" instance " +
notification.getValueId().getInstance()
);
data.put("uuid", device.getUuid());
data.put("label", Manager.get().getValueLabel(notification.getValueId()));
data.put("data", String.valueOf(Utils.getValue(notification.getValueId())));
messaging.broadcast("event.devices.zwave.value.added", new GenericAdvertisement("ZWaveValueAdded", data));
}
// enable value polling TODO
//Manager.get().enablePoll(notification.getValueId());
break;
case VALUE_REMOVED:
device = Device.getDeviceByNode(node);
if (device == null) {
LOGGER.info("While save remove value, node " + node + " not found");
break;
}
device.removeValue(manager.getValueLabel(notification.getValueId()));
data.put("uuid", device.getUuid());
data.put("label", Manager.get().getValueLabel(notification.getValueId()));
data.put("data", String.valueOf(Utils.getValue(notification.getValueId())));
messaging.broadcast("event.devices.zwave.value.removed", new GenericAdvertisement("ZWaveValueRemoved", data));
if (!manager.getValueLabel(notification.getValueId()).isEmpty()) {
LOGGER.info("Node " + device.getNode() + ": Value " + manager.getValueLabel(notification.getValueId()) + " removed");
}
break;
case VALUE_CHANGED:
device = Device.getDeviceByNode(node);
if (device == null) {
break;
}
// Check for awaked after sleeping nodes
if (manager.isNodeAwake(homeId, device.getNode()) && device.getStatus().equals("Sleeping")) {
LOGGER.info("Setting node " + device.getNode() + " to LISTEN state");
device.setStatus("Listening");
}
LOGGER.info("Node " +
device.getNode() + ": " +
"Value for label \"" + manager.getValueLabel(notification.getValueId()) + "\" changed --> " +
"\"" + Utils.getValue(notification.getValueId()) + "\"");
DeviceValue udvChg = device.getValue(manager.getValueLabel(notification.getValueId()));
if (udvChg != null)
device.removeValue(udvChg);
else
udvChg = new DeviceValue();
udvChg.setLabel(manager.getValueLabel(notification.getValueId()));
udvChg.setValueType(Utils.getValueType(notification.getValueId()));
udvChg.setValueId(notification.getValueId());
udvChg.setValueUnits(Manager.get().getValueUnits(notification.getValueId()));
udvChg.setValue(String.valueOf(Utils.getValue(notification.getValueId())));
udvChg.setReadonly(Manager.get().isValueReadOnly(notification.getValueId()));
device.addValue(udvChg);
DBLogger.info("Value " + manager.getValueLabel(notification.getValueId()) + " changed: " + Utils.getValue(notification.getValueId()), device.getUuid());
SensorData.log(device.getUuid(), Manager.get().getValueLabel(notification.getValueId()), String.valueOf(Utils.getValue(notification.getValueId())));
data.put("uuid", device.getUuid());
data.put("label", Manager.get().getValueLabel(notification.getValueId()));
data.put("data", String.valueOf(Utils.getValue(notification.getValueId())));
messaging.broadcast("event.devices.zwave.value.changed", new GenericAdvertisement("ZWaveValueChanged", data));
break;
case VALUE_REFRESHED:
LOGGER.info("Node " + node + ": Value refreshed (" +
" command class: " + notification.getValueId().getCommandClassId() + ", " +
" instance: " + notification.getValueId().getInstance() + ", " +
" index: " + notification.getValueId().getIndex() + ", " +
" value: " + Utils.getValue(notification.getValueId()));
break;
case GROUP:
break;
case SCENE_EVENT:
break;
case CREATE_BUTTON:
break;
case DELETE_BUTTON:
break;
case BUTTON_ON:
break;
case BUTTON_OFF:
break;
case NOTIFICATION:
break;
default:
LOGGER.info(notification.getType().name());
break;
}
// save device and values
if (device != null)
device.save();
}
};
manager.addWatcher(watcher, null);
manager.addDriver(config.get("zwavePort"));
LOGGER.info("Waiting while ZWave finish initialization");
// Ждем окончания инициализации
while (!ready) {
Thread.sleep(1000);
LOGGER.info("Still waiting");
}
// set polling interval 60 sec TODO
//Manager.get().setPollInterval(60000, true);
for (Device ZWaveDevice : Ebean.find(Device.class).where().eq("source", "zwave").findList()) {
// Check for dead nodes
if (Manager.get().isNodeFailed(homeId, ZWaveDevice.getNode())) {
LOGGER.info("Setting node " + ZWaveDevice.getNode() + " to DEAD state");
ZWaveDevice.setStatus("dead");
ZWaveDevice.save();
}
// Check for sleeping nodes
if (!Manager.get().isNodeAwake(homeId, ZWaveDevice.getNode())) {
LOGGER.info("Setting node " + ZWaveDevice.getNode() + " to SLEEP state");
ZWaveDevice.setStatus("sleeping");
Manager.get().refreshNodeInfo(homeId, ZWaveDevice.getNode());
ZWaveDevice.save();
}
}
LOGGER.info("Initialization complete.");
messaging.subscribe("event.devices.setvalue");
messaging.subscribe("event.devices.zwave.value.set");
messaging.subscribe("event.devices.zwave.node.add");
messaging.subscribe("event.devices.zwave.node.remove");
messaging.subscribe("event.devices.zwave.cancel");
messaging.start();
messaging.setNotification(new JsonNotification() {
@Override
public void onNotification(JsonEnvelope envelope) {
if (envelope.getObject() instanceof GenericAdvertisement) {
GenericAdvertisement advertisement = envelope.getObject();
switch (advertisement.getLabel()) {
case "DeviceOn":
deviceSetLevel(advertisement, 255);
break;
case "DeviceOff":
deviceSetLevel(advertisement, 0);
break;
case "DeviceSetLevel":
deviceSetLevel(advertisement, null);
break;
case "ZWaveAddNode":
LOGGER.info("Set controller into AddDevice mode");
Manager.get().beginControllerCommand(homeId, ControllerCommand.ADD_DEVICE, new CallbackListener(ControllerCommand.ADD_DEVICE), null, true);
break;
case "ZWaveRemoveNode":
LOGGER.info("Set controller into RemoveDevice mode");
Manager.get().beginControllerCommand(homeId, ControllerCommand.REMOVE_DEVICE, new CallbackListener(ControllerCommand.REMOVE_DEVICE), null, true, (short) advertisement.getValue());
break;
case "ZWaveCancelCommand":
LOGGER.info("Canceling controller command");
Manager.get().cancelControllerCommand(homeId);
break;
}
} else {
// We received unknown request message. Lets make generic log entry.
LOGGER.info("Received request "
+ " from " + envelope.getSenderInstance()
+ " to " + envelope.getReceiverInstance()
+ " at '" + envelope.getSubject()
+ ": " + envelope.getObject());
}
}
});
// Close JSON messaging.
messaging.start();
} catch (final Throwable t) {
LOGGER.error("Error in ZWave: " + t);
status.crashed();
}
}
private void setTypedValue(ValueId valueId, String value) {
LOGGER.debug("Set type " + valueId.getType() + " to label " + Manager.get().getValueLabel(valueId));
switch (valueId.getType()) {
case BOOL:
LOGGER.debug("Set value type BOOL to " + value);
Manager.get().setValueAsBool(valueId, Boolean.valueOf(value));
break;
case BYTE:
LOGGER.debug("Set value type BYTE to " + value);
Manager.get().setValueAsByte(valueId, Short.valueOf(value));
break;
case DECIMAL:
LOGGER.debug("Set value type FLOAT to " + value);
Manager.get().setValueAsFloat(valueId, Float.valueOf(value));
break;
case INT:
LOGGER.debug("Set value type INT to " + value);
Manager.get().setValueAsInt(valueId, Integer.valueOf(value));
break;
case LIST:
LOGGER.debug("Set value type LIST to " + value);
break;
case SCHEDULE:
LOGGER.debug("Set value type SCHEDULE to " + value);
break;
case SHORT:
LOGGER.debug("Set value type SHORT to " + value);
Manager.get().setValueAsShort(valueId, Short.valueOf(value));
break;
case STRING:
LOGGER.debug("Set value type STRING to " + value);
Manager.get().setValueAsString(valueId, value);
break;
case BUTTON:
LOGGER.debug("Set value type BUTTON to " + value);
break;
case RAW:
LOGGER.debug("Set value RAW to " + value);
break;
default:
break;
}
}
private void setValue(String uuid, String label, Integer value) {
Device device = Device.getDeviceByUUID(uuid);
DeviceValue zv = device.getValue(label);
if (zv != null) {
if (!Manager.get().isValueReadOnly(gson.fromJson(zv.getValueId(), ValueId.class))) {
setTypedValue(gson.fromJson(zv.getValueId(), ValueId.class), String.valueOf(value));
} else {
LOGGER.info("Value \"%s\" is read-only! Skip.", label);
}
}
}
private Device addZWaveDeviceOrValue(String type, Notification notification) {
Device ZWaveDevice;
String label = Manager.get().getValueLabel(notification.getValueId());
String state = "not responding";
String productName = Manager.get().getNodeProductName(notification.getHomeId(), notification.getNodeId());
String manufName = Manager.get().getNodeManufacturerName(notification.getHomeId(), notification.getNodeId());
if (Manager.get().requestNodeState(homeId, notification.getNodeId())) {
state = "listening";
}
if ((ZWaveDevice = Ebean.find(Device.class).where().eq("internalname", "zwave/" + type + "/" + notification.getNodeId()).findUnique()) == null) {
String uuid = UUID.randomUUID().toString();
ZWaveDevice = new Device();
ZWaveDevice.setInternalType(type);
ZWaveDevice.setSource("zwave");
ZWaveDevice.setInternalName("zwave/" + type + "/" + notification.getNodeId());
ZWaveDevice.setType(Manager.get().getNodeType(notification.getHomeId(), notification.getNodeId()));
ZWaveDevice.setNode(notification.getNodeId());
ZWaveDevice.setUuid(uuid);
ZWaveDevice.setManufName(manufName);
ZWaveDevice.setProductName(productName);
ZWaveDevice.setStatus(state);
ZWaveDevice.addValue(new DeviceValue(
label,
uuid,
String.valueOf(Utils.getValue(notification.getValueId())),
Utils.getValueType(notification.getValueId()),
Manager.get().getValueUnits(notification.getValueId()),
notification.getValueId(),
Manager.get().isValueReadOnly(notification.getValueId())
));
// Check if it is beaming device
DeviceValue beaming = new DeviceValue();
beaming.setLabel("beaming");
beaming.setValueId("{ }");
beaming.setValue(String.valueOf(Manager.get().isNodeBeamingDevice(homeId, ZWaveDevice.getNode())));
beaming.setReadonly(true);
ZWaveDevice.addValue(beaming);
LOGGER.info("Adding device " + type + " (node: " + notification.getNodeId() + ") to system");
} else {
ZWaveDevice.setManufName(manufName);
ZWaveDevice.setProductName(productName);
ZWaveDevice.setStatus(state);
// check empty label
if (label.isEmpty())
return ZWaveDevice;
LOGGER.info("Node " + ZWaveDevice.getNode() + ": Add \"" + label + "\" value \"" + Utils.getValue(notification.getValueId()) + "\"");
DeviceValue udv = ZWaveDevice.getValue(label);
// remove
if (udv != null) {
ZWaveDevice.removeValue(udv);
} else {
udv = new DeviceValue();
}
udv.setLabel(label);
udv.setValueType(Utils.getValueType(notification.getValueId()));
udv.setValueId(notification.getValueId());
udv.setValueUnits(Manager.get().getValueUnits(notification.getValueId()));
udv.setValue(String.valueOf(Utils.getValue(notification.getValueId())));
udv.setReadonly(Manager.get().isValueReadOnly(notification.getValueId()));
ZWaveDevice.addValue(udv);
}
return ZWaveDevice;
}
private void deviceSetLevel(GenericAdvertisement advertisement, Integer level) {
String label = "Level";
String uuid;
if (level == null) {
level = (Integer) advertisement.getValue("data");
label = (String) advertisement.getValue("label");
uuid = (String) advertisement.getValue();
} else {
uuid = (String) advertisement.getValue();
}
Device ZWaveDevice = Device.getDeviceByUUID(uuid);
if (ZWaveDevice != null && !ZWaveDevice.getSource().equals("zwave")) {
// not zwave
return;
}
if (ZWaveDevice == null) {
LOGGER.info("Cant find device with UUID " + uuid);
return;
}
int node = ZWaveDevice.getNode();
if (!ZWaveDevice.getStatus().equals("Dead")) {
LOGGER.info("Setting value: " + level + " to label \"" + label + "\" on node " + node + " (UUID: " + uuid + ")");
setValue(uuid, label, level);
} else {
LOGGER.info("Node: " + node + " Cant set empty value or node dead");
}
}
private class CallbackListener implements ControllerCallback {
private ControllerCommand ctl;
public CallbackListener(ControllerCommand ctl) {
this.ctl = ctl;
}
@Override
public void onCallback(ControllerState state, ControllerError err, Object context) {
LOGGER.debug("ZWave Command Callback: {} , {}", state, err);
if (ctl == ControllerCommand.REMOVE_DEVICE && state == ControllerState.COMPLETED) {
LOGGER.info("Remove ZWave device from network");
Manager.get().softReset(homeId);
Manager.get().testNetwork(homeId, 5);
Manager.get().healNetwork(homeId, true);
}
if (ctl == ControllerCommand.ADD_DEVICE && state == ControllerState.COMPLETED) {
LOGGER.info("Add ZWave device to network");
Manager.get().testNetwork(homeId, 5);
Manager.get().healNetwork(homeId, true);
}
}
}
public void stop() {
messaging.close();
manager.removeWatcher(watcher, null);
manager.removeDriver(config.get("zwavePort"));
}
}