/*
* Dog - Z-Wave
*
* Copyright 2013 Davide Aimone and Dario Bonino
*
* 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
*/
package it.polito.elite.dog.drivers.zwave.gateway;
import it.polito.elite.dog.core.devicefactory.api.DeviceFactory;
import it.polito.elite.dog.core.library.model.ControllableDevice;
import it.polito.elite.dog.core.library.model.DeviceDescriptor;
import it.polito.elite.dog.core.library.model.DeviceDescriptorFactory;
import it.polito.elite.dog.core.library.model.DeviceStatus;
import it.polito.elite.dog.core.library.model.devicecategory.Controllable;
import it.polito.elite.dog.core.library.model.devicecategory.ZWaveGateway;
import it.polito.elite.dog.core.library.model.state.DeviceAssociationState;
import it.polito.elite.dog.core.library.model.state.State;
import it.polito.elite.dog.core.library.model.statevalue.AssociatingStateValue;
import it.polito.elite.dog.core.library.model.statevalue.DisassociatingStateValue;
import it.polito.elite.dog.core.library.model.statevalue.IdleStateValue;
import it.polito.elite.dog.core.library.util.LogHelper;
import it.polito.elite.dog.drivers.zwave.ZWaveAPI;
import it.polito.elite.dog.drivers.zwave.model.zway.json.Controller;
import it.polito.elite.dog.drivers.zwave.model.zway.json.DataConst;
import it.polito.elite.dog.drivers.zwave.model.zway.json.Device;
import it.polito.elite.dog.drivers.zwave.model.zway.json.DeviceData;
import it.polito.elite.dog.drivers.zwave.model.zway.json.Instance;
import it.polito.elite.dog.drivers.zwave.network.ZWaveDriverInstance;
import it.polito.elite.dog.drivers.zwave.network.info.ZWaveNodeInfo;
import it.polito.elite.dog.drivers.zwave.network.interfaces.ZWaveNetwork;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.LogService;
public class ZWaveGatewayDriverInstance extends ZWaveDriverInstance implements
ZWaveGateway
{
// the driver logger
LogHelper logger;
// the log identifier, unique for the class
public static String LOG_ID = "[ZWaveGatewayDriverInstance]: ";
// data controller associated with the gateway
protected Controller controller;
// the current list of devices for which dynamic creation can be done
private ConcurrentHashMap<String, String> supportedDevices;
// the device factory reference
private DeviceFactory deviceFactory;
// the device descriptor factory reference
private DeviceDescriptorFactory descriptorFactory;
// last included device;
private int lastIncludedDevice = -1;
// last excluded device
private int lastExcludedDevice = -1;
// enable dynamic device detection
private boolean detectionEnabled = false;
// the time to wait before attempting automatic device detection
private long waitBeforeDeviceInstall = 0;
public ZWaveGatewayDriverInstance(ZWaveNetwork network,
DeviceFactory deviceFactory, ControllableDevice controllableDevice,
int nodeId, Set<Integer> instancesId, BundleContext context)
{
// gateway driver node contains always multiple instanceId, but only the
// one with Id = 0 contains interesting data
// also updateTimeMillis = 0 is fixed to zero because we are not
// interested din this kind of behavior fot the gateway
super(network, controllableDevice, nodeId, instancesId, nodeId, 0,
context);
// store the device factory reference
this.deviceFactory = deviceFactory;
// create a logger
logger = new LogHelper(context);
// create the device descriptor factory
try
{
this.descriptorFactory = new DeviceDescriptorFactory(context
.getBundle().getEntry("/deviceTemplates"));
}
catch (Exception e)
{
this.logger.log(LogService.LOG_ERROR,
"Error while creating DeviceDescriptorFactory ", e);
}
// create a new device state (according to the current DogOnt model, no
// state is actually associated to a Modbus gateway)
currentState = new DeviceStatus(device.getDeviceId());
// initialize device states
this.initializeStates();
}
/**
* Updates the inner state accordingly to the given State instance and
* triggers a device state update
*
* @param newState
* The new State instance to update the inner state
*/
private boolean changeState(State newState)
{
//the state changed flag
boolean stateChanged = false;
// get the current state
String currentStateValue = "";
State state = currentState.getState(DeviceAssociationState.class
.getSimpleName());
if (state != null)
currentStateValue = (String) state.getCurrentStateValue()[0]
.getValue();
// check that the state has changed
if (!currentStateValue.equals(newState.getCurrentStateValue()[0]
.getValue()))
{
// update the current state
this.currentState.setState(
DeviceAssociationState.class.getSimpleName(), newState);
// debug
logger.log(LogService.LOG_DEBUG, ZWaveGatewayDriverInstance.LOG_ID
+ "Device " + device.getDeviceId() + " is now "
+ (newState).getCurrentStateValue()[0].getValue());
// update the status
this.updateStatus();
//updated the state changed flag
stateChanged = true;
}
return stateChanged;
}
/**
* starts inclusion process that lasts for 20 seconds
*/
@Override
public void associate()
{
// start inclusion mode
network.controllerWrite(ZWaveGatewayDriver.CMD_INCLUDE, "1");
}
/**
* starts exclusion process that lasts for 20 seconds
*/
@Override
public void disassociate() // TODO: remove String nodeID
{
// start exclusion mode
network.controllerWrite(ZWaveGatewayDriver.CMD_EXCLUDE, "1");
}
/**
* starts learn process that lasts for 20 seconds. Learn mode is equivalent
* to press the button on the device (not only gateway) to start include
* process. Due this means that the device is not yet included in the
* system, this method is left empty for future purposes
*/
// @Override TODO: add notation
public void learn()
{
// Nothing to do...
}
/**
* reset Z-Wave controller to default. NB: will completely destroy all
* stored data about your network!
*/
// @Override TODO: add notation
public void reset()
{
network.controllerWrite(ZWaveGatewayDriver.CMD_RESET, "");
}
@Override
public synchronized DeviceStatus getState()
{
return this.currentState;
}
@Override
protected void specificConfiguration()
{
// nothing to do...
}
@Override
protected void addToNetworkDriver(ZWaveNodeInfo nodeInfo)
{
network.addDriver(nodeInfo, 0, this);
}
@Override
public void newMessageFromHouse(Device deviceNode, Instance instanceNode,
Controller controllerNode, String sValue)
{
this.deviceNode = deviceNode;
controller = controllerNode;
/*-------------- HANDLE ASSOCIATION ------------------------*/
// check if dynamic device detection is enabled
if (detectionEnabled)
{
// check if any new device has been recently associated
int lastIncludedDeviceAtController = controller.getData()
.getLastIncludedDevice();
if ((lastIncludedDeviceAtController != -1)
// checks that the device is not the last included before
&& (lastIncludedDeviceAtController != this.lastIncludedDevice)
// checks that the device is not already included and
// running
&& (this.network
.getControllableDeviceURIFromNodeId(lastIncludedDeviceAtController) == null)
// checks that there are supported devices
&& (this.supportedDevices != null)
&& (!this.supportedDevices.isEmpty()))
{
// get the device data
Device newDeviceData = this.network
.getRawDevice(lastIncludedDeviceAtController);
// build the device descriptor
DeviceDescriptor descriptorToAdd = this.buildDeviceDescriptor(
newDeviceData, lastIncludedDeviceAtController);
// check not null
if (descriptorToAdd != null)
{
// create the device
// cross the finger
this.deviceFactory.addNewDevice(descriptorToAdd);
// only when the device has been created update the
// last included device
this.lastIncludedDevice = lastIncludedDeviceAtController;
// disable dynamic detection until a new association is
// detected
this.detectionEnabled = false;
}
}
}
/*-------------- HANDLE DISASSOCIATION ------------------------*/
// check if any new device has been recently disassociated
int lastExcludedDeviceAtController = controller.getData()
.getLastExcludedDevice();
if ((lastExcludedDeviceAtController != -1)
&& (lastExcludedDeviceAtController != this.lastExcludedDevice))
{
// update the last included device
this.lastExcludedDevice = lastExcludedDeviceAtController;
// get the device URI
String deviceId = this.network
.getControllableDeviceURIFromNodeId(this.lastExcludedDevice);
// remove the device (if not null)
if ((deviceId != null) && (!deviceId.isEmpty()))
{
this.deviceFactory.removeDevice(deviceId);
}
// remove the device association
// TODO: this should be done by the device driver, check how to
this.network.removeDriver(this.lastExcludedDevice);
}
/*-------------- HANDLE STATE ----------------------------*/
if (this.currentState != null)
{
int controllerState = this.controller.getData()
.getControllerState();
// handle controller states
switch (controllerState)
{
case 0: // idle
{
State currentAssociationState = this.currentState
.getState(DeviceAssociationState.class.getSimpleName());
if ((currentAssociationState != null)
&& (currentAssociationState.getCurrentStateValue()[0]
.getClass().getName()
.equals(AssociatingStateValue.class.getName())))
{
// enable dynamic device detection
this.detectionEnabled = true;
}
if(this.changeState(new DeviceAssociationState(
new IdleStateValue())))
// notify the current idle state
this.notifyIdle();
break;
}
case 1: // associating
{
if(this.changeState(new DeviceAssociationState(
new AssociatingStateValue())))
// notify the current associating state
this.notifyAssociating();
break;
}
case 5: // disassociating
{
if(this.changeState(new DeviceAssociationState(
new DisassociatingStateValue())))
// notify the current disassociating state
this.notifyDisassociating();
break;
}
default:
{
break;
}
}
}
}
@Override
protected boolean isController()
{
return true;
}
@Override
protected ZWaveNodeInfo createNodeInfo(int deviceId,
Set<Integer> instancesId, boolean isController)
{
HashMap<Integer, Set<Integer>> instanceCommand = new HashMap<Integer, Set<Integer>>();
// this is the gateway so we are not really interested in command class
// for sensor data update
HashSet<Integer> ccSet = new HashSet<Integer>();
ccSet.add(ZWaveAPI.COMMAND_CLASS_BASIC);
for (Integer instanceId : instancesId)
{
instanceCommand.put(instanceId, ccSet);
}
ZWaveNodeInfo nodeInfo = new ZWaveNodeInfo(deviceId, instanceCommand,
isController);
return nodeInfo;
}
/**
* @return the supportedDevices
*/
public ConcurrentHashMap<String, String> getSupportedDevices()
{
return supportedDevices;
}
/**
* @param supportedDevices
* the supportedDevices to set
*/
public void setSupportedDevices(
ConcurrentHashMap<String, String> supportedDevices)
{
// simplest updated policy : replacement
this.supportedDevices = supportedDevices;
// debug
this.logger.log(LogService.LOG_DEBUG,
"Updated dynamic device creation db");
}
/**
* Gets the time to wait before automatic device detection, in milliseconds
*
* @return
*/
public long getWaitBeforeDeviceInstall()
{
return waitBeforeDeviceInstall;
}
/**
* Sets the time to wait before automatic device detection, in milliseconds
*
* @param waitBeforeDeviceInstall
*/
public void setWaitBeforeDeviceInstall(long waitBeforeDeviceInstall)
{
this.waitBeforeDeviceInstall = waitBeforeDeviceInstall;
}
// TODO: implement better.... just a trial
private DeviceDescriptor buildDeviceDescriptor(Device device, int nodeId)
{
// the device descriptor to return
DeviceDescriptor descriptor = null;
if (this.descriptorFactory != null)
{
// get the new device data
DeviceData deviceData = device.getData();
// get the manufacturer id
String manufacturerId = deviceData.getAllData()
.get(DataConst.MANUFACTURER_ID).getValue().toString();
String manufacturerProductType = deviceData.getAllData()
.get(DataConst.MANUFACTURER_PRODUCT_TYPE).getValue()
.toString();
String manufacturerProductId = deviceData.getAllData()
.get(DataConst.MANUFACTURER_PRODUCT_ID).getValue()
.toString();
// wait for instances to be read.... (may be read with a certain
// variable delay)
try
{
Thread.sleep(this.waitBeforeDeviceInstall);
}
catch (InterruptedException e1)
{
this.logger
.log(LogService.LOG_WARNING,
"Instance wait time was less than necessary due to interrupted thread, device instantiation might not be accurate.",
e1);
}
// build the 4th id (number of instances)
int numberOfInstances = network.getRawDevice(nodeId).getInstances()
.size();
// build the device unique id
String extendedDeviceUniqueId = manufacturerId + "-"
+ manufacturerProductType + "-" + manufacturerProductId
+ "-" + numberOfInstances;
// build the device unique id
String deviceUniqueId = manufacturerId + "-"
+ manufacturerProductType + "-" + manufacturerProductId;
// get the device class
String deviceClass = this.supportedDevices
.get(extendedDeviceUniqueId);
// check if not extended
if (deviceClass == null)
deviceClass = this.supportedDevices.get(deviceUniqueId);
// normal workflow...
if ((deviceClass != null) && (!deviceClass.isEmpty()))
{
// create a descriptor definition map
HashMap<String, Object> descriptorDefinitionData = new HashMap<String, Object>();
// store the device name
descriptorDefinitionData.put(DeviceDescriptorFactory.NAME,
deviceClass + "_" + nodeId);
// store the device description
descriptorDefinitionData.put(
DeviceDescriptorFactory.DESCRIPTION,
"New Device of type " + deviceClass);
// store the device gateway
descriptorDefinitionData.put(DeviceDescriptorFactory.GATEWAY,
this.device.getDeviceId());
// store the device location
descriptorDefinitionData.put(DeviceDescriptorFactory.LOCATION,
"");
// store the node id
descriptorDefinitionData.put("nodeId", "" + nodeId);
// handle multiple instances
String instanceIds[] = new String[device.getInstances().size()];
// mine instances
int i = 0;
for (Integer instanceId : device.getInstances().keySet())
{
instanceIds[i] = instanceId.toString();
i++;
}
// store mined instances
descriptorDefinitionData.put("instanceIds", instanceIds);
// get the device descriptor
try
{
descriptor = this.descriptorFactory.getDescriptor(
descriptorDefinitionData, deviceClass);
}
catch (Exception e)
{
this.logger
.log(LogService.LOG_ERROR,
"Error while creating DeviceDescriptor for the just added device ",
e);
}
// debug dump
this.logger.log(LogService.LOG_INFO,
"Detected new device: \n\tdeviceUniqueId: "
+ deviceUniqueId + "\n\tnodeId: " + nodeId
+ "\n\tdeviceClass: " + deviceClass);
}
}
// return
return descriptor;
}
/**
* Initializes the state asynchronously as required by OSGi
*/
private void initializeStates()
{
// initialize the state
this.currentState.setState(
DeviceAssociationState.class.getSimpleName(),
new DeviceAssociationState(new IdleStateValue()));
// get the initial state of the device
Runnable worker = new Runnable()
{
public void run()
{
network.read(nodeInfo, true);
}
};
Thread workerThread = new Thread(worker);
workerThread.start();
}
@Override
public void notifyAssociating()
{
((ZWaveGateway) this.device).notifyAssociating();
}
@Override
public void notifyDisassociating()
{
((ZWaveGateway) this.device).notifyDisassociating();
}
@Override
public void notifyIdle()
{
((ZWaveGateway) this.device).notifyIdle();
}
@Override
public void updateStatus()
{
// update the monitor admin status snapshot
((Controllable) this.device).updateStatus();
}
}