/*
* 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.powermeteringlevelcontrollableoutput;
import it.polito.elite.dog.core.library.model.ControllableDevice;
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.EnergyAndPowerMeteringLevelControllableOutput;
import it.polito.elite.dog.core.library.model.devicecategory.LevelControllableOutput;
import it.polito.elite.dog.core.library.model.devicecategory.PowerMeteringLevelControllableOutput;
import it.polito.elite.dog.core.library.model.state.LevelState;
import it.polito.elite.dog.core.library.model.state.OnOffState;
import it.polito.elite.dog.core.library.model.state.SinglePhaseActiveEnergyState;
import it.polito.elite.dog.core.library.model.state.SinglePhaseActivePowerMeasurementState;
import it.polito.elite.dog.core.library.model.state.State;
import it.polito.elite.dog.core.library.model.statevalue.ActiveEnergyStateValue;
import it.polito.elite.dog.core.library.model.statevalue.ActivePowerStateValue;
import it.polito.elite.dog.core.library.model.statevalue.LevelStateValue;
import it.polito.elite.dog.core.library.model.statevalue.OffStateValue;
import it.polito.elite.dog.core.library.model.statevalue.OnStateValue;
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.CommandClasses;
import it.polito.elite.dog.drivers.zwave.model.zway.json.Controller;
import it.polito.elite.dog.drivers.zwave.model.zway.json.DataElemObject;
import it.polito.elite.dog.drivers.zwave.model.zway.json.Device;
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 javax.measure.DecimalMeasure;
import javax.measure.Measure;
import javax.measure.quantity.Dimensionless;
import javax.measure.unit.NonSI;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import javax.measure.unit.UnitFormat;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.LogService;
public class ZWavePowerMeteringLevelControllableOutputDriverInstance extends
ZWaveDriverInstance implements PowerMeteringLevelControllableOutput,
EnergyAndPowerMeteringLevelControllableOutput
{
// the class logger
private LogHelper logger;
// the step increase / decrease
private int stepPercentage = 5; // default
// the group set
private HashSet<Integer> groups;
// the scene set
private HashSet<Integer> scenes;
public ZWavePowerMeteringLevelControllableOutputDriverInstance(
ZWaveNetwork network, ControllableDevice device, int deviceId,
Set<Integer> instancesId, int gatewayNodeId, int updateTimeMillis,
int stepPercentage, BundleContext context)
{
super(network, device, deviceId, instancesId, gatewayNodeId,
updateTimeMillis, context);
this.stepPercentage = stepPercentage;
// build inner data structures
this.groups = new HashSet<Integer>();
this.scenes = new HashSet<Integer>();
// create a logger
logger = new LogHelper(context);
// initialize states
this.initializeStates();
}
/**
* Initializes the state asynchronously as required by OSGi
*/
private void initializeStates()
{
// add unit of measure aliases (to fix notation problems...)
UnitFormat uf = UnitFormat.getInstance();
uf.alias(SI.WATT.times(NonSI.HOUR), "Wh");
uf.label(SI.KILO(SI.WATT.times(NonSI.HOUR)), "kWh");
// initialize the state
this.currentState.setState(OnOffState.class.getSimpleName(),
new OnOffState(new OffStateValue()));
// only if MeteringDimmablePowerOutlet
if (this.device instanceof EnergyAndPowerMeteringLevelControllableOutput)
{
// initialize the energy state value
ActiveEnergyStateValue energyStateValue = new ActiveEnergyStateValue();
energyStateValue.setValue(DecimalMeasure.valueOf("0.0 "
+ SI.KILO(SI.WATT.times(NonSI.HOUR)).toString()));
this.currentState.setState(
SinglePhaseActiveEnergyState.class.getSimpleName(),
new SinglePhaseActiveEnergyState(energyStateValue));
}
// initialize the power state value
ActivePowerStateValue powerStateValue = new ActivePowerStateValue();
powerStateValue.setValue(DecimalMeasure.valueOf("0.0 "
+ SI.WATT.toString()));
this.currentState.setState(
SinglePhaseActivePowerMeasurementState.class.getSimpleName(),
new SinglePhaseActivePowerMeasurementState(powerStateValue));
LevelStateValue levelValue = new LevelStateValue();
levelValue.setValue(DecimalMeasure.valueOf(0, Unit.ONE));
this.currentState.setState(LevelState.class.getSimpleName(),
new LevelState(levelValue));
// 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 newMessageFromHouse(Device deviceNode, Instance instanceNode,
Controller controllerNode, String sValue)
{
this.deviceNode = deviceNode;
// the on/off updated flag
boolean updatedOnOff = false;
// the level updated flag
boolean updatedLevel = false;
// the energy updated flag
boolean energyUpdated = false;
// the power updated flag
boolean powerUpdated = false;
// Read the value associated with the right CommandClass.
// switch multi-level
int nLevel = 0;
CommandClasses ccEntry = instanceNode
.getCommandClass(ZWaveAPI.COMMAND_CLASS_SWITCH_MULTILEVEL);
// Check if it is a real new value or if it is an old one
if (ccEntry != null)
{
nLevel = ccEntry.getLevelAsInt();
// change the state and update corresponding flags
updatedOnOff = changeOnOffState((nLevel > 0) ? OnOffState.ON
: OnOffState.OFF);
updatedLevel = changeLevelState(nLevel);
}
// meter
CommandClasses ccElectricityEntry = instanceNode.getCommandClasses()
.get(ZWaveAPI.COMMAND_CLASS_METER);
// Check if it is a real new value or if it is an old one. We can use
// one of the cc available
DataElemObject instance0 = ccElectricityEntry.get("0");
if (instance0 != null)
{
long updateTime = instance0.getDataElem("val").getUpdateTime();
// first time we only save update time, no more
if (lastUpdateTime == 0)
lastUpdateTime = updateTime;
else if (lastUpdateTime < updateTime)
{
// update last update time
lastUpdateTime = updateTime;
nFailedUpdate = 0;
// handle energy data if available
DataElemObject energyEntry = ccElectricityEntry.get("0");
if (energyEntry != null)
{
this.changeActiveEnergyState(Double.valueOf(energyEntry
.getDataElemValue("val").toString()));
energyUpdated = true;
}
// handle power data if available
DataElemObject powerEntry = ccElectricityEntry.get("2");
if (powerEntry != null)
{
this.changeActivePowerState(Double.valueOf(powerEntry
.getDataElemValue("val").toString()));
powerUpdated = true;
}
}
}
if (updatedLevel || updatedOnOff || powerUpdated || energyUpdated)
this.updateStatus();
}
/**
* Manages the power state update (only updates if the current state value
* is different from the given one)
*
* @param activeEnergy
* @return
*/
private void changeActiveEnergyState(double activeEnergy)
{
// build the energy measure
DecimalMeasure<?> value = DecimalMeasure.valueOf(activeEnergy + " "
+ SI.KILO(SI.WATT.times(NonSI.HOUR)).toString());
// update the state
ActiveEnergyStateValue pValue = new ActiveEnergyStateValue();
pValue.setValue(value);
currentState.setState(
SinglePhaseActiveEnergyState.class.getSimpleName(),
new SinglePhaseActiveEnergyState(pValue));
// debug
logger.log(LogService.LOG_DEBUG, "Device " + device.getDeviceId()
+ " active energy " + value.toString());
// notify energy change
this.notifyNewActiveEnergyValue(value);
}
/**
* Manages the energy state update (only updates if the current state value
* is different from the given one)
*
* @param activePower
* @return
*/
private void changeActivePowerState(double activePower)
{
// build the power measure
DecimalMeasure<?> powerValue = DecimalMeasure.valueOf(activePower + " "
+ SI.WATT.toString());
// update the state
ActivePowerStateValue pValue = new ActivePowerStateValue();
pValue.setValue(powerValue);
currentState.setState(
SinglePhaseActivePowerMeasurementState.class.getSimpleName(),
new SinglePhaseActivePowerMeasurementState(pValue));
// debug
logger.log(LogService.LOG_DEBUG, "Device " + device.getDeviceId()
+ " active power " + powerValue.toString());
// notify the state change
this.notifyNewActivePowerValue(powerValue);
}
/**
* Check if the current state has been changed. In that case, fire a state
* change message, otherwise it does nothing
*
* @param OnOffValue
* OnOffState.ON or OnOffState.OFF
*/
private boolean changeOnOffState(String OnOffValue)
{
// state changed flag
boolean stateChanged = false;
// get the current state value
String currentStateValue = "";
State state = currentState.getState(OnOffState.class.getSimpleName());
if (state != null)
currentStateValue = (String) state.getCurrentStateValue()[0]
.getValue();
// if the current states it is different from the new state
if (!currentStateValue.equalsIgnoreCase(OnOffValue))
{
State newState;
// set the new state to on or off...
if (OnOffValue.equalsIgnoreCase(OnOffState.ON))
{
newState = new OnOffState(new OnStateValue());
// send the on notification
this.notifyOn();
}
else
{
newState = new OnOffState(new OffStateValue());
// send the off notification
this.notifyOff();
}
// ... then set the new state for the device and throw a state
// changed notification
currentState.setState(newState.getStateName(), newState);
// state changed
stateChanged = true;
}
return stateChanged;
}
/**
* Manages the state change operations for the level state
*
* @param nLevel
* @return
*/
@SuppressWarnings("unchecked")
private boolean changeLevelState(int nLevel)
{
// flag for state changes
boolean stateChanged = false;
// get the current state
// get the current state value
Measure<?, Dimensionless> currentStateValue = null;
State state = currentState.getState(LevelState.class.getSimpleName());
if (state != null)
currentStateValue = (Measure<?, Dimensionless>) state
.getCurrentStateValue()[0].getValue();
// check if the state is changed or not
if ((currentStateValue != null)
&& (currentStateValue.intValue(Unit.ONE) != nLevel))
{
// update the state
LevelStateValue pValue = new LevelStateValue();
pValue.setValue(DecimalMeasure.valueOf(nLevel, Unit.ONE));
// change the current level state
currentState.setState(LevelState.class.getSimpleName(),
new LevelState(pValue));
// send the changed level notification
this.notifyChangedLevel(DecimalMeasure.valueOf(nLevel, Unit.ONE));
// the state is changed
stateChanged = true;
}
// debug
logger.log(LogService.LOG_DEBUG, "Device " + device.getDeviceId()
+ " dimmer at " + nLevel);
return stateChanged;
}
@Override
protected void specificConfiguration()
{
// prepare the device state map
currentState = new DeviceStatus(device.getDeviceId());
}
@Override
protected void addToNetworkDriver(ZWaveNodeInfo nodeInfo)
{
network.addDriver(nodeInfo, updateTimeMillis, this);
}
@Override
protected boolean isController()
{
return false;
}
@Override
public DeviceStatus getState()
{
return currentState;
}
@Override
public void on()
{
// Sends on command to all instances, probably only one in this case
for (Integer instanceId : nodeInfo.getInstanceSet())
network.write(nodeInfo.getDeviceNodeId(), instanceId,
ZWaveAPI.COMMAND_CLASS_SWITCH_MULTILEVEL, "255");
}
@Override
public void off()
{
// Sends off command to all instances, probably only one in this case
for (Integer instanceId : nodeInfo.getInstanceSet())
network.write(nodeInfo.getDeviceNodeId(), instanceId,
ZWaveAPI.COMMAND_CLASS_SWITCH_MULTILEVEL, "0");
}
@Override
protected ZWaveNodeInfo createNodeInfo(int deviceId,
Set<Integer> instancesId, boolean isController)
{
HashMap<Integer, Set<Integer>> instanceCommand = new HashMap<Integer, Set<Integer>>();
HashSet<Integer> ccSet = new HashSet<Integer>();
ccSet.add(ZWaveAPI.COMMAND_CLASS_SWITCH_MULTILEVEL);
ccSet.add(ZWaveAPI.COMMAND_CLASS_METER);
// binary switch has its own command class
for (Integer instanceId : instancesId)
{
instanceCommand.put(instanceId, ccSet);
}
ZWaveNodeInfo nodeInfo = new ZWaveNodeInfo(deviceId, instanceCommand,
isController);
return nodeInfo;
}
@Override
public void set(Object value)
{
// Sends on command to all instances, probably only one in this case
for (Integer instanceId : nodeInfo.getInstanceSet())
network.write(nodeInfo.getDeviceNodeId(), instanceId,
ZWaveAPI.COMMAND_CLASS_SWITCH_MULTILEVEL, value.toString()
+ ", 0");
}
@Override
public void stepDown()
{
// get the current level value
int currentValue = (Integer) currentState.getState(
LevelState.class.getSimpleName()).getCurrentStateValue()[0]
.getValue();
// set to 100%
if (currentValue == 255)
currentValue = 100;
// decrease by step percentage
currentValue = Math.max(0, currentValue - this.stepPercentage);
// set the value
this.set(currentValue);
}
@Override
public void stepUp()
{
// get the current level value
int currentValue = (Integer) currentState.getState(
LevelState.class.getSimpleName()).getCurrentStateValue()[0]
.getValue();
// increase by step percentage
currentValue = Math.min(100, currentValue + this.stepPercentage);
// set the value
this.set(currentValue);
}
@Override
public void storeScene(Integer sceneNumber)
{
// Store the given scene id
this.scenes.add(sceneNumber);
// notify
this.notifyStoredScene(sceneNumber);
}
@Override
public void deleteScene(Integer sceneNumber)
{
// Remove the given scene id
this.scenes.remove(sceneNumber);
// notify
this.notifyDeletedScene(sceneNumber);
}
@Override
public void deleteGroup(Integer groupID)
{
// remove the given group id
this.groups.remove(groupID);
// notify
this.notifyLeftGroup(groupID);
}
@Override
public void storeGroup(Integer groupID)
{
// Store the given group id
this.groups.add(groupID);
this.notifyJoinedGroup(groupID);
}
@Override
public void notifyStoredScene(Integer sceneNumber)
{
// send the store scene notification
((LevelControllableOutput) this.device).notifyStoredScene(sceneNumber);
}
@Override
public void notifyDeletedScene(Integer sceneNumber)
{
// send the delete scene notification
((LevelControllableOutput) this.device).notifyDeletedScene(sceneNumber);
}
@Override
public void notifyJoinedGroup(Integer groupNumber)
{
// send the joined group notification
((LevelControllableOutput) this.device).notifyJoinedGroup(groupNumber);
}
@Override
public void notifyLeftGroup(Integer groupNumber)
{
// send the left group notification
((LevelControllableOutput) this.device).notifyLeftGroup(groupNumber);
}
@Override
public void notifyOn()
{
// send the on notification corresponding to the on action carried
// (internally or externally) on the device.
((LevelControllableOutput) this.device).notifyOn();
}
@Override
public void notifyChangedLevel(Measure<?, ?> newLevel)
{
// send the changed level notification corresponding to the new state of
// the device.
((LevelControllableOutput) this.device).notifyChangedLevel(newLevel);
}
@Override
public void notifyOff()
{
// send the off notification corresponding to the on action carried
// (internally or externally) on the device.
((LevelControllableOutput) this.device).notifyOff();
}
@Override
public void updateStatus()
{
// update the monitor admin status snapshot
((Controllable) this.device).updateStatus();
}
@Override
public Measure<?, ?> getActivePower()
{
return (Measure<?, ?>) currentState.getState(
SinglePhaseActivePowerMeasurementState.class.getSimpleName())
.getCurrentStateValue()[0].getValue();
}
@Override
public void notifyNewActivePowerValue(Measure<?, ?> powerValue)
{
// update the state
ActivePowerStateValue pValue = new ActivePowerStateValue();
pValue.setValue(powerValue);
currentState.setState(
SinglePhaseActivePowerMeasurementState.class.getSimpleName(),
new SinglePhaseActivePowerMeasurementState(pValue));
// debug
logger.log(LogService.LOG_DEBUG, "Device " + device.getDeviceId()
+ " active power " + powerValue.toString());
// notify the new measure
((EnergyAndPowerMeteringLevelControllableOutput) device)
.notifyNewActivePowerValue(powerValue);
}
@Override
public Measure<?, ?> getActiveEnergyValue()
{
return (Measure<?, ?>) currentState.getState(
SinglePhaseActiveEnergyState.class.getSimpleName())
.getCurrentStateValue()[0].getValue();
}
@Override
public void notifyNewActiveEnergyValue(Measure<?, ?> value)
{
// update the state
ActiveEnergyStateValue pValue = new ActiveEnergyStateValue();
pValue.setValue(value);
currentState.setState(
SinglePhaseActiveEnergyState.class.getSimpleName(),
new SinglePhaseActiveEnergyState(pValue));
// debug
logger.log(LogService.LOG_DEBUG, "Device " + device.getDeviceId()
+ " active energy " + value.toString());
// notify the new measure
((EnergyAndPowerMeteringLevelControllableOutput) device)
.notifyNewActiveEnergyValue(value);
}
}