/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.binding.digitalstrom.DigitalSTROMBindingConstants;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.config.Config;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.DeviceStatusListener;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.serverConnection.constants.JSONApiResponseKeysEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.Device;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.ChangeableDeviceConfigEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.DSID;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.DeviceConstants;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.DeviceSceneSpec;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.DeviceStateUpdate;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.DeviceStateUpdateImpl;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.FunctionalColorGroupEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.JSONDeviceSceneSpecImpl;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceParameters.OutputModeEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.scene.InternalScene;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.scene.constants.SceneEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
* The {@link JSONDeviceImpl} is the implementation of the {@link Device}.
*
* @author Michael Ochel - Initial contribution
* @author Matthias Siegele - Initial contribution
*/
public class JSONDeviceImpl implements Device {
private static final Logger logger = LoggerFactory.getLogger(JSONDeviceImpl.class);
private Config config;
private DeviceStatusListener listener = null;
private DSID dsid = null;
private DSID meterDSID = null;
private String dSUID = null;
private String name = null;
private int zoneId = 0;
private List<Short> groupList = new LinkedList<Short>();
private FunctionalColorGroupEnum functionalGroup = null;
private String hwInfo;
private boolean isPresent = false;
private OutputModeEnum outputMode = null;
private boolean isOn = false;
private boolean isOpen = true;
private short outputValue = 0;
private short maxOutputValue = DeviceConstants.DEFAULT_MAX_OUTPUTVALUE;
private short minOutputValue = 0;
private short slatAngle = 0;
private short maxSlatAngle = DeviceConstants.MAX_SLAT_ANGLE;
private short minSlatAngle = DeviceConstants.MIN_SLAT_ANGLE;
private int slatPosition = 0;
private int maxSlatPosition = DeviceConstants.MAX_ROLLERSHUTTER;
private int minSlatPosition = DeviceConstants.MIN_ROLLERSHUTTER;
private int activePower = 0;
private int outputCurrent = 0;
private int electricMeter = 0;
// for scenes
private short activeSceneNumber = -1;
private InternalScene activeScene = null;
private InternalScene lastScene = null;
private int outputValueBeforeSceneCall = 0;
private short slatAngleBeforeSceneCall = 0;
private boolean lastCallWasUndo = false;
private Map<Short, DeviceSceneSpec> sceneConfigMap = Collections
.synchronizedMap(new HashMap<Short, DeviceSceneSpec>());
private Map<Short, Integer[]> sceneOutputMap = Collections.synchronizedMap(new HashMap<Short, Integer[]>());
// saves outstanding commands
private List<DeviceStateUpdate> deviceStateUpdates = Collections
.synchronizedList(new LinkedList<DeviceStateUpdate>());
// save the last update time of the sensor data
private long lastElectricMeterUpdate = System.currentTimeMillis();
private long lastOutputCurrentUpdate = System.currentTimeMillis();
private long lastActivePowerUpdate = System.currentTimeMillis();
// this flags are true, if a sensorJob is initiated to add it to the sensorJobExecuter by the deviceStateUpdates
// list.
private boolean electricMeterUpdateInitiated = false;
private boolean outputCurrentUpdateInitiated = false;
private boolean activePowerUpdateInitiated = false;
// sensor data refresh priorities
private String activePowerRefreshPriority = Config.REFRESH_PRIORITY_NEVER;
private String electricMeterRefreshPriority = Config.REFRESH_PRIORITY_NEVER;
private String outputCurrentRefreshPriority = Config.REFRESH_PRIORITY_NEVER;
/*
* Cache the last MeterValues to get MeterData directly
* the key is the output value and the value is an Integer array for the meter data (0 = powerConsumption, 1 =
* electricMeter, 2 =EnergyMeter)
*/
private Map<Short, Integer[]> cachedSensorMeterData = Collections.synchronizedMap(new HashMap<Short, Integer[]>());
/**
* Creates a new {@link JSONDeviceImpl} from the given DigitalSTROM-Device {@link JsonObject}.
*
* @param group json object
*/
public JSONDeviceImpl(JsonObject object) {
if (object.get(JSONApiResponseKeysEnum.DEVICE_NAME.getKey()) != null) {
this.name = object.get(JSONApiResponseKeysEnum.DEVICE_NAME.getKey()).getAsString();
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_ID.getKey()) != null) {
this.dsid = new DSID(object.get(JSONApiResponseKeysEnum.DEVICE_ID.getKey()).getAsString());
} else if (object.get(JSONApiResponseKeysEnum.DEVICE_ID_QUERY.getKey()) != null) {
this.dsid = new DSID(object.get(JSONApiResponseKeysEnum.DEVICE_ID_QUERY.getKey()).getAsString());
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_METER_ID.getKey()) != null) {
this.meterDSID = new DSID(object.get(JSONApiResponseKeysEnum.DEVICE_METER_ID.getKey()).getAsString());
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_DSUID.getKey()) != null) {
this.dSUID = object.get(JSONApiResponseKeysEnum.DEVICE_DSUID.getKey()).getAsString();
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_ID.getKey()) != null) {
this.hwInfo = object.get(JSONApiResponseKeysEnum.DEVICE_HW_INFO.getKey()).getAsString();
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_ON.getKey()) != null) {
if (!isShade()) {
this.isOn = object.get(JSONApiResponseKeysEnum.DEVICE_ON.getKey()).getAsBoolean();
} else {
this.isOpen = object.get(JSONApiResponseKeysEnum.DEVICE_ON.getKey()).getAsBoolean();
}
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_IS_PRESENT.getKey()) != null) {
this.isPresent = object.get(JSONApiResponseKeysEnum.DEVICE_IS_PRESENT.getKey()).getAsBoolean();
} else if (object.get(JSONApiResponseKeysEnum.DEVICE_IS_PRESENT_QUERY.getKey()) != null) {
this.isPresent = object.get(JSONApiResponseKeysEnum.DEVICE_IS_PRESENT_QUERY.getKey()).getAsBoolean();
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_ZONE_ID.getKey()) != null) {
zoneId = object.get(JSONApiResponseKeysEnum.DEVICE_ZONE_ID.getKey()).getAsInt();
} else if (object.get(JSONApiResponseKeysEnum.DEVICE_ZONE_ID_QUERY.getKey()) != null) {
zoneId = object.get(JSONApiResponseKeysEnum.DEVICE_ZONE_ID_QUERY.getKey()).getAsInt();
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_GROUPS.getKey()) instanceof JsonArray) {
JsonArray array = (JsonArray) object.get(JSONApiResponseKeysEnum.DEVICE_GROUPS.getKey());
for (int i = 0; i < array.size(); i++) {
if (array.get(i) != null) {
short tmp = array.get(i).getAsShort();
if (tmp != -1) {
this.groupList.add(tmp);
if (FunctionalColorGroupEnum.containsColorGroup((int) tmp)) {
if (this.functionalGroup == null || !FunctionalColorGroupEnum.getColorGroup((int) tmp)
.equals(FunctionalColorGroupEnum.BLACK)) {
this.functionalGroup = FunctionalColorGroupEnum.getColorGroup((int) tmp);
}
}
}
}
}
}
if (object.get(JSONApiResponseKeysEnum.DEVICE_OUTPUT_MODE.getKey()) != null) {
int tmp = object.get(JSONApiResponseKeysEnum.DEVICE_OUTPUT_MODE.getKey()).getAsInt();
if (tmp != -1) {
if (OutputModeEnum.containsMode(tmp)) {
outputMode = OutputModeEnum.getMode(tmp);
}
}
}
init();
}
private void init() {
if (groupList.contains((short) 1)) {
maxOutputValue = DeviceConstants.MAX_OUTPUT_VALUE_LIGHT;
if (this.isDimmable()) {
minOutputValue = DeviceConstants.MIN_DIM_VALUE;
}
} else {
maxOutputValue = DeviceConstants.DEFAULT_MAX_OUTPUTVALUE;
minOutputValue = 0;
}
if (isOn) {
outputValue = DeviceConstants.DEFAULT_MAX_OUTPUTVALUE;
}
}
@Override
public DSID getDSID() {
return dsid;
}
@Override
public String getDSUID() {
return this.dSUID;
}
@Override
public synchronized DSID getMeterDSID() {
return this.meterDSID;
}
@Override
public synchronized void setMeterDSID(String meterDSID) {
this.meterDSID = new DSID(meterDSID);
if (listener != null) {
listener.onDeviceConfigChanged(ChangeableDeviceConfigEnum.METER_DSID);
}
}
@Override
public String getHWinfo() {
return hwInfo;
}
@Override
public synchronized String getName() {
return this.name;
}
@Override
public synchronized void setName(String name) {
this.name = name;
if (listener != null) {
listener.onDeviceConfigChanged(ChangeableDeviceConfigEnum.DEVICE_NAME);
}
}
@Override
public List<Short> getGroups() {
return new LinkedList<Short>(groupList);
}
@Override
public void addGroup(Short groupID) {
if (!this.groupList.contains(groupID)) {
this.groupList.add(groupID);
}
if (listener != null) {
listener.onDeviceConfigChanged(ChangeableDeviceConfigEnum.GROUPS);
}
}
@Override
public void setGroups(List<Short> newGroupList) {
if (newGroupList != null) {
this.groupList = newGroupList;
}
if (listener != null) {
listener.onDeviceConfigChanged(ChangeableDeviceConfigEnum.GROUPS);
}
}
@Override
public synchronized int getZoneId() {
return zoneId;
}
@Override
public synchronized void setZoneId(int zoneID) {
this.zoneId = zoneID;
if (listener != null) {
listener.onDeviceConfigChanged(ChangeableDeviceConfigEnum.ZONE_ID);
}
}
@Override
public synchronized boolean isPresent() {
return isPresent;
}
@Override
public synchronized void setIsPresent(boolean isPresent) {
this.isPresent = isPresent;
if (listener != null) {
if (!isPresent) {
listener.onDeviceRemoved(this);
} else {
listener.onDeviceAdded(this);
}
}
}
@Override
public synchronized boolean isOn() {
return isOn;
}
@Override
public synchronized void setIsOn(boolean flag) {
if (flag) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, 1));
} else {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, -1));
}
}
@Override
public synchronized boolean isOpen() {
return this.isOpen;
}
@Override
public synchronized void setIsOpen(boolean flag) {
if (flag) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OPEN_CLOSE, 1));
if (isBlind()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OPEN_CLOSE_ANGLE, 1));
}
} else {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OPEN_CLOSE, -1));
if (isBlind()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OPEN_CLOSE_ANGLE, -1));
}
}
}
@Override
public synchronized void setOutputValue(short value) {
if (!isShade()) {
if (value <= 0) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, -1));
} else if (value > maxOutputValue) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, 1));
} else {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS, value));
}
}
}
@Override
public synchronized boolean isDimmable() {
return OutputModeEnum.outputModeIsDimmable(outputMode);
}
@Override
public synchronized boolean isSwitch() {
return OutputModeEnum.outputModeIsSwitch(outputMode);
}
@Override
public synchronized boolean isDeviceWithOutput() {
return this.outputMode != null && !this.outputMode.equals(OutputModeEnum.DISABLED);
}
@Override
public synchronized FunctionalColorGroupEnum getFunctionalColorGroup() {
return this.functionalGroup;
}
@Override
public synchronized void setFunctionalColorGroup(FunctionalColorGroupEnum fuctionalColorGroup) {
this.functionalGroup = fuctionalColorGroup;
if (listener != null) {
listener.onDeviceConfigChanged(ChangeableDeviceConfigEnum.FUNCTIONAL_GROUP);
}
}
@Override
public OutputModeEnum getOutputMode() {
return outputMode;
}
@Override
public synchronized void setOutputMode(OutputModeEnum newOutputMode) {
this.outputMode = newOutputMode;
if (listener != null) {
listener.onDeviceConfigChanged(ChangeableDeviceConfigEnum.OUTPUT_MODE);
}
}
@Override
public synchronized void increase() {
if (isDimmable()) {
deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS_INCREASE, 0));
}
if (isShade()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_INCREASE, 0));
if (isBlind()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE_DECREASE, 0));
}
}
}
@Override
public synchronized void decrease() {
if (isDimmable()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS_DECREASE, 0));
}
if (isShade()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_DECREASE, 0));
if (isBlind()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE_DECREASE, 0));
}
}
}
@Override
public synchronized void increaseSlatAngle() {
if (isBlind()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE_DECREASE, 1));
}
}
@Override
public synchronized void decreaseSlatAngle() {
if (isBlind()) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE_DECREASE, 1));
}
}
@Override
public synchronized short getOutputValue() {
return outputValue;
}
@Override
public short getMaxOutputValue() {
return maxOutputValue;
}
@Override
public short getMinOutputValue() {
return minOutputValue;
}
@Override
public boolean isShade() {
return OutputModeEnum.outputModeIsShade(outputMode);
}
@Override
public boolean isBlind() {
return outputMode.equals(OutputModeEnum.POSITION_CON_US);
}
@Override
public synchronized int getSlatPosition() {
return slatPosition;
}
@Override
public synchronized short getAnglePosition() {
return slatAngle;
}
@Override
public synchronized void setAnglePosition(int angle) {
if (angle == slatAngle) {
return;
}
if (angle < minSlatAngle) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE, minSlatAngle));
} else if (angle > this.maxSlatPosition) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE, maxSlatAngle));
} else {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE, angle));
}
}
@Override
public synchronized void setSlatPosition(int position) {
if (position == this.slatPosition) {
return;
}
if (position < minSlatPosition) {
this.deviceStateUpdates
.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLATPOSITION, minSlatPosition));
} else if (position > this.maxSlatPosition) {
this.deviceStateUpdates
.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLATPOSITION, maxSlatPosition));
} else {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLATPOSITION, position));
}
}
@Override
public synchronized int getActivePower() {
return activePower;
}
@Override
public synchronized void setActivePower(int activePower) {
activePowerUpdateInitiated = false;
if (activePower >= 0) {
lastActivePowerUpdate = System.currentTimeMillis();
if (activePower == this.activePower) {
return;
}
int standby = Config.DEFAULT_STANDBY_ACTIVE_POWER;
if (config != null) {
standby = config.getStandbyActivePower();
}
if (outputMode.equals(OutputModeEnum.WIPE) && !isOn && activePower > standby) {
this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, 1));
}
informListenerAboutStateUpdate(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ACTIVE_POWER, activePower));
this.activePower = activePower;
this.addActivePowerToMeterCache(this.getOutputValue(), activePower);
}
}
@Override
public synchronized int getOutputCurrent() {
return outputCurrent;
}
@Override
public synchronized void setOutputCurrent(int outputCurrent) {
outputCurrentUpdateInitiated = false;
if (outputCurrent >= 0) {
lastOutputCurrentUpdate = System.currentTimeMillis();
if (outputCurrent == this.outputCurrent) {
return;
}
informListenerAboutStateUpdate(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OUTPUT_CURRENT, outputCurrent));
this.addEnergyMeterToMeterCache(this.getOutputValue(), outputCurrent);
this.outputCurrent = outputCurrent;
}
}
@Override
public synchronized int getElectricMeter() {
return electricMeter;
}
@Override
public synchronized void setElectricMeter(int electricMeter) {
electricMeterUpdateInitiated = false;
if (electricMeter >= 0) {
lastElectricMeterUpdate = System.currentTimeMillis();
if (electricMeter == this.electricMeter) {
return;
}
informListenerAboutStateUpdate(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ELECTRIC_METER, electricMeter));
this.electricMeter = electricMeter;
}
}
private void addActivePowerToMeterCache(short outputValue, int activePower) {
Integer[] cachedMeterData = cachedSensorMeterData.get(outputValue);
if (cachedMeterData == null) {
cachedMeterData = new Integer[2];
}
cachedMeterData[0] = activePower;
this.cachedSensorMeterData.put(outputValue, cachedMeterData);
}
private void addEnergyMeterToMeterCache(short outputValue, int energyMeter) {
Integer[] cachedMeterData = cachedSensorMeterData.get(outputValue);
if (cachedMeterData == null) {
cachedMeterData = new Integer[2];
}
cachedMeterData[1] = energyMeter;
this.cachedSensorMeterData.put(outputValue, cachedMeterData);
}
private short getDimmStep() {
if (isDimmable()) {
return DeviceConstants.DIM_STEP_LIGHT;
} else if (isShade()) {
return DeviceConstants.MOVE_STEP_ROLLERSHUTTER;
} else {
return DeviceConstants.DEFAULT_MOVE_STEP;
}
}
@Override
public int getMaxSlatPosition() {
return maxSlatPosition;
}
@Override
public int getMinSlatPosition() {
return minSlatPosition;
}
@Override
public int getMaxSlatAngle() {
return maxSlatAngle;
}
@Override
public int getMinSlatAngle() {
return minSlatAngle;
}
/* Begin-Scenes */
@Override
public synchronized void callInternalScene(InternalScene scene) {
if (lastCallWasUndo) {
lastScene = null;
if (activeScene != null) {
activeScene.deactivateSceneByDevice();
}
activeScene = null;
}
internalCallScene(scene.getSceneID());
activeScene = scene;
lastCallWasUndo = false;
}
@Override
public void checkSceneConfig(Short sceneNumber, int prio) {
if (isDeviceWithOutput()) {
if (!containsSceneConfig(sceneNumber)) {
deviceStateUpdates
.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_CONFIG, prio + sceneNumber));
}
if (sceneOutputMap.get(sceneNumber) == null) {
deviceStateUpdates
.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_OUTPUT, prio + sceneNumber));
}
}
}
@Override
public synchronized void undoInternalScene(InternalScene scene) {
logger.debug("undo Scene {} dSID {}", scene.getSceneID(), dsid.getValue());
if (activeScene != null && activeScene.equals(scene)) {
if (lastCallWasUndo) {
lastScene = null;
return;
}
if (this.lastScene != null && !lastScene.equals(activeScene)) {
activeScene = lastScene;
lastScene = null;
activeScene.activateSceneByDevice();
} else {
internalUndoScene();
logger.debug("internalUndo Scene dSID " + dsid.getValue());
this.activeScene = null;
}
lastCallWasUndo = true;
}
}
@Override
public synchronized void callScene(Short sceneNumber) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_CALL_SCENE, sceneNumber));
}
@Override
public synchronized void internalCallScene(Short sceneNumber) {
logger.debug("call Scene id {} dSID {}", sceneNumber, dsid.getValue());
if (isDeviceWithOutput()) {
activeSceneNumber = sceneNumber;
informLastSceneAboutSceneCall(sceneNumber);
if (!isShade()) {
outputValueBeforeSceneCall = this.outputValue;
} else {
outputValueBeforeSceneCall = this.slatPosition;
if (isBlind()) {
slatAngleBeforeSceneCall = this.slatAngle;
}
}
if (!checkSceneNumber(sceneNumber)) {
if (containsSceneConfig(sceneNumber)) {
if (doIgnoreScene(sceneNumber)) {
return;
}
} else {
this.deviceStateUpdates
.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_CONFIG, sceneNumber));
}
if (sceneOutputMap.get(sceneNumber) != null) {
if (!isShade()) {
updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS,
sceneOutputMap.get(sceneNumber)[0]));
} else {
updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLATPOSITION,
sceneOutputMap.get(sceneNumber)[0]));
if (isBlind()) {
updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE,
sceneOutputMap.get(sceneNumber)[1]));
}
}
} else {
this.deviceStateUpdates
.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SCENE_OUTPUT, sceneNumber));
}
}
}
}
private boolean checkSceneNumber(Short sceneNumber) {
if (this.outputMode.equals(OutputModeEnum.POWERSAVE)) {
switch (SceneEnum.getScene(sceneNumber)) {
case ABSENT:
case DEEP_OFF:
case SLEEPING:
this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, -1));
return true;
case AREA_1_OFF:
case AREA_2_OFF:
case AREA_3_OFF:
case AREA_4_OFF:
case PRESET_0:
case PRESET_10:
case PRESET_20:
case PRESET_30:
case PRESET_40:
return true;
default:
break;
}
}
if (this.outputMode.equals(OutputModeEnum.WIPE)) {
switch (SceneEnum.getScene(sceneNumber)) {
case STANDBY:
case AUTO_STANDBY:
case AREA_1_OFF:
case AREA_2_OFF:
case AREA_3_OFF:
case AREA_4_OFF:
case PRESET_0:
case PRESET_10:
case PRESET_20:
case PRESET_30:
case PRESET_40:
this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, -1));
return true;
default:
break;
}
}
switch (SceneEnum.getScene(sceneNumber)) {
// on scenes
case DEVICE_ON:
case MAXIMUM:
if (!isShade()) {
this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, 1));
} else {
this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OPEN_CLOSE, 1));
if (isBlind()) {
this.updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OPEN_CLOSE_ANGLE, 1));
}
}
return true;
// off scenes
case MINIMUM:
case DEVICE_OFF:
case AUTO_OFF:
if (!isShade()) {
this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ON_OFF, -1));
} else {
this.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OPEN_CLOSE, -1));
if (isBlind()) {
this.updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OPEN_CLOSE_ANGLE, -1));
}
}
return true;
// increase scenes
case INCREMENT:
case AREA_1_INCREMENT:
case AREA_2_INCREMENT:
case AREA_3_INCREMENT:
case AREA_4_INCREMENT:
if (isDimmable()) {
if (outputValue == maxOutputValue) {
return true;
}
this.updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS_INCREASE, 0));
}
if (isShade()) {
if (slatPosition == maxSlatPosition) {
return true;
}
this.updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_INCREASE, 0));
if (isBlind()) {
if (slatAngle == maxSlatAngle) {
return true;
}
updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE_INCREASE, 0));
}
}
return true;
// decrease scenes
case DECREMENT:
case AREA_1_DECREMENT:
case AREA_2_DECREMENT:
case AREA_3_DECREMENT:
case AREA_4_DECREMENT:
if (isDimmable()) {
if (outputValue == minOutputValue) {
return true;
}
updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS_DECREASE, 0));
}
if (isShade()) {
if (slatPosition == minSlatPosition) {
return true;
}
updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_DECREASE, 0));
if (isBlind()) {
if (slatAngle == minSlatAngle) {
return true;
}
this.updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE_INCREASE, 0));
}
}
return true;
// Stop scenes
case AREA_1_STOP:
case AREA_2_STOP:
case AREA_3_STOP:
case AREA_4_STOP:
case DEVICE_STOP:
case STOP:
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OUTPUT_VALUE, 0));
return true;
// Area Stepping continue scenes
case AREA_STEPPING_CONTINUE:
// TODO: we don't know what will be happened when this scene was called. Some one know it?
return true;
default:
return false;
}
}
private Integer[] getStandartSceneOutput(short sceneNumber) {
switch (SceneEnum.getScene(sceneNumber)) {
case DEVICE_ON:
case MAXIMUM:
if (!isShade()) {
return new Integer[] { (int) maxOutputValue, -1 };
} else {
if (isBlind()) {
return new Integer[] { (int) maxSlatPosition, (int) maxSlatAngle };
} else {
return new Integer[] { (int) maxSlatPosition, -1 };
}
}
// off scenes
case MINIMUM:
case DEVICE_OFF:
case AUTO_OFF:
if (!isShade()) {
return new Integer[] { (int) 0, -1 };
} else {
if (isBlind()) {
return new Integer[] { (int) 0, 0 };
} else {
return new Integer[] { (int) 0, -1 };
}
}
default:
break;
}
return null;
}
private void informLastSceneAboutSceneCall(short sceneNumber) {
if (this.activeScene != null && this.activeScene.getSceneID() != sceneNumber) {
this.activeScene.deactivateSceneByDevice();
this.lastScene = this.activeScene;
this.activeScene = null;
}
}
@Override
public synchronized void undoScene() {
this.deviceStateUpdates
.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_UNDO_SCENE, this.activeSceneNumber));
}
@Override
public synchronized void internalUndoScene() {
if (!isShade()) {
updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS, this.outputValueBeforeSceneCall));
} else {
updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLATPOSITION, this.outputValueBeforeSceneCall));
if (isBlind()) {
updateInternalDeviceState(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE, this.slatAngleBeforeSceneCall));
}
}
if (activeSceneNumber != -1) {
activeSceneNumber = -1;
}
}
@Override
public InternalScene getAcitiveScene() {
return this.activeScene;
}
@Override
public Integer[] getSceneOutputValue(short sceneId) {
synchronized (sceneOutputMap) {
if (sceneOutputMap.containsKey(sceneId)) {
return sceneOutputMap.get(sceneId);
}
}
return new Integer[] { -1, -1 };
}
@Override
public void setSceneOutputValue(short sceneId, int value) {
synchronized (sceneOutputMap) {
sceneOutputMap.put(sceneId, new Integer[] { value, -1 });
if (listener != null) {
listener.onSceneConfigAdded(sceneId);
}
}
}
@Override
public void setSceneOutputValue(short sceneId, int value, int angle) {
synchronized (sceneOutputMap) {
sceneOutputMap.put(sceneId, new Integer[] { value, angle });
if (listener != null) {
listener.onSceneConfigAdded(sceneId);
}
}
}
@Override
public List<Short> getSavedScenes() {
Set<Short> bothKeySet = new HashSet<Short>(sceneOutputMap.keySet());
bothKeySet.addAll(sceneConfigMap.keySet());
return new LinkedList<Short>(bothKeySet);
}
@Override
public void addSceneConfig(short sceneId, DeviceSceneSpec sceneSpec) {
if (sceneSpec != null) {
synchronized (sceneConfigMap) {
sceneConfigMap.put(sceneId, sceneSpec);
if (listener != null) {
listener.onSceneConfigAdded(sceneId);
}
}
}
}
@Override
public DeviceSceneSpec getSceneConfig(short sceneId) {
synchronized (sceneConfigMap) {
return sceneConfigMap.get(sceneId);
}
}
@Override
public boolean doIgnoreScene(short sceneId) {
synchronized (sceneConfigMap) {
if (this.sceneConfigMap.containsKey(sceneId)) {
return this.sceneConfigMap.get(sceneId).isDontCare();
}
}
return false;
}
@Override
public boolean containsSceneConfig(short sceneId) {
synchronized (sceneConfigMap) {
return sceneConfigMap.containsKey(sceneId);
}
}
/* End-Scenes */
@Override
public boolean equals(Object obj) {
if (obj instanceof Device) {
Device device = (Device) obj;
return device.getDSID().equals(this.getDSID());
}
return false;
}
@Override
public int hashCode() {
return this.getDSID().hashCode();
}
@Override
public boolean isActivePowerUpToDate() {
return (outputMode.equals(OutputModeEnum.WIPE) && !isOn)
|| (isOn && !isShade()) && !this.activePowerRefreshPriority.contains(Config.REFRESH_PRIORITY_NEVER)
? checkSensorRefreshTime(lastActivePowerUpdate) : true;
}
@Override
public boolean isElectricMeterUpToDate() {
return (isOn || this.electricMeter == 0) && !isShade()
&& !this.electricMeterRefreshPriority.contains(Config.REFRESH_PRIORITY_NEVER)
? checkSensorRefreshTime(lastElectricMeterUpdate) : true;
}
@Override
public boolean isOutputCurrentUpToDate() {
return isOn && !isShade() && !this.outputCurrentRefreshPriority.contains(Config.REFRESH_PRIORITY_NEVER)
? checkSensorRefreshTime(lastOutputCurrentUpdate) : true;
}
private boolean checkSensorRefreshTime(long lastTime) {
int refresh = Config.DEFAULT_SENSORDATA_REFRESH_INTERVAL;
if (config != null) {
refresh = config.getSensordataRefreshInterval();
}
return (lastTime + refresh) > System.currentTimeMillis();
}
@Override
public boolean isSensorDataUpToDate() {
return isActivePowerUpToDate() && isElectricMeterUpToDate() && isOutputCurrentUpToDate();
}
@Override
public void setSensorDataRefreshPriority(String activePowerRefreshPriority, String electricMeterRefreshPriority,
String outputCurrentRefreshPriority) {
if (checkPriority(activePowerRefreshPriority) != null) {
this.activePowerRefreshPriority = activePowerRefreshPriority;
}
if (checkPriority(electricMeterRefreshPriority) != null) {
this.electricMeterRefreshPriority = electricMeterRefreshPriority;
}
if (checkPriority(outputCurrentRefreshPriority) != null) {
this.outputCurrentRefreshPriority = outputCurrentRefreshPriority;
}
}
private String checkPriority(String priority) {
switch (priority) {
case Config.REFRESH_PRIORITY_HIGH:
break;
case Config.REFRESH_PRIORITY_MEDIUM:
break;
case Config.REFRESH_PRIORITY_LOW:
break;
case Config.REFRESH_PRIORITY_NEVER:
break;
default:
logger.error("Sensor data update priority do not exist! Please check the input!");
return null;
}
return priority;
}
@Override
public String getActivePowerRefreshPriority() {
if (outputMode.equals(OutputModeEnum.WIPE) && !isOn) {
return Config.REFRESH_PRIORITY_LOW;
}
return this.activePowerRefreshPriority;
}
@Override
public String getElectricMeterRefreshPriority() {
return this.electricMeterRefreshPriority;
}
@Override
public String getOutputCurrentRefreshPriority() {
return this.outputCurrentRefreshPriority;
}
@Override
public boolean isDeviceUpToDate() {
if (!isActivePowerUpToDate()) {
if (!activePowerUpdateInitiated) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ACTIVE_POWER, 0));
activePowerUpdateInitiated = true;
}
}
if (!isElectricMeterUpToDate()) {
if (!electricMeterUpdateInitiated) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ELECTRIC_METER, 0));
electricMeterUpdateInitiated = true;
}
}
if (!isOutputCurrentUpToDate()) {
if (!outputCurrentUpdateInitiated) {
this.deviceStateUpdates.add(new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OUTPUT_CURRENT, 0));
outputCurrentUpdateInitiated = true;
}
}
return this.deviceStateUpdates.isEmpty();
}
@Override
public DeviceStateUpdate getNextDeviceUpdateState() {
return !this.deviceStateUpdates.isEmpty() ? this.deviceStateUpdates.remove(0) : null;
}
private int internalSetOutputValue(int value) {
if (isShade()) {
slatPosition = value;
if (slatPosition <= 0) {
this.slatPosition = 0;
this.isOpen = false;
} else {
this.isOpen = true;
}
return slatPosition;
} else {
outputValue = (short) value;
if (outputValue <= 0) {
this.isOn = false;
setActivePower(0);
setOutputCurrent(0);
electricMeterUpdateInitiated = false;
} else {
this.isOn = true;
setCachedMeterData();
}
return outputValue;
}
}
private short internalSetAngleValue(int value) {
if (value < 0) {
slatAngle = 0;
}
if (value > maxSlatAngle) {
slatAngle = maxSlatAngle;
} else {
slatAngle = (short) value;
}
return slatAngle;
}
@Override
public synchronized void updateInternalDeviceState(DeviceStateUpdate deviceStateUpdate) {
if (deviceStateUpdate != null) {
switch (deviceStateUpdate.getType()) {
case DeviceStateUpdate.UPDATE_BRIGHTNESS_DECREASE:
deviceStateUpdate = new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS_DECREASE,
internalSetOutputValue(outputValue - getDimmStep()));
break;
case DeviceStateUpdate.UPDATE_BRIGHTNESS_INCREASE:
deviceStateUpdate = new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_BRIGHTNESS_INCREASE,
internalSetOutputValue(outputValue + getDimmStep()));
break;
case DeviceStateUpdate.UPDATE_BRIGHTNESS:
internalSetOutputValue(deviceStateUpdate.getValue());
break;
case DeviceStateUpdate.UPDATE_ON_OFF:
if (deviceStateUpdate.getValue() < 0) {
internalSetOutputValue(0);
} else {
internalSetOutputValue(maxOutputValue);
}
break;
case DeviceStateUpdate.UPDATE_OPEN_CLOSE:
if (deviceStateUpdate.getValue() < 0) {
internalSetOutputValue(0);
} else {
internalSetOutputValue(maxSlatPosition);
}
break;
case DeviceStateUpdate.UPDATE_OPEN_CLOSE_ANGLE:
if (deviceStateUpdate.getValue() < 0) {
internalSetAngleValue(0);
} else {
internalSetAngleValue(maxSlatAngle);
}
break;
case DeviceStateUpdate.UPDATE_SLAT_DECREASE:
deviceStateUpdate = new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_DECREASE,
internalSetOutputValue(slatPosition - getDimmStep()));
break;
case DeviceStateUpdate.UPDATE_SLAT_INCREASE:
deviceStateUpdate = new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_INCREASE,
internalSetOutputValue(slatPosition + getDimmStep()));
case DeviceStateUpdate.UPDATE_SLATPOSITION:
internalSetOutputValue(deviceStateUpdate.getValue());
break;
case DeviceStateUpdate.UPDATE_SLAT_ANGLE_DECREASE:
deviceStateUpdate = new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE_DECREASE,
internalSetAngleValue(slatAngle - DeviceConstants.ANGLE_STEP_SLAT));
break;
case DeviceStateUpdate.UPDATE_SLAT_ANGLE_INCREASE:
deviceStateUpdate = new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_SLAT_ANGLE_INCREASE,
internalSetAngleValue(slatAngle + DeviceConstants.ANGLE_STEP_SLAT));
break;
case DeviceStateUpdate.UPDATE_SLAT_ANGLE:
internalSetAngleValue(deviceStateUpdate.getValue());
break;
case DeviceStateUpdate.UPDATE_ELECTRIC_METER:
setElectricMeter(deviceStateUpdate.getValue());
return;
case DeviceStateUpdate.UPDATE_OUTPUT_CURRENT:
setOutputCurrent(deviceStateUpdate.getValue());
return;
case DeviceStateUpdate.UPDATE_ACTIVE_POWER:
setActivePower(deviceStateUpdate.getValue());
return;
case DeviceStateUpdate.UPDATE_CALL_SCENE:
this.internalCallScene((short) deviceStateUpdate.getValue());
return;
case DeviceStateUpdate.UPDATE_UNDO_SCENE:
this.internalUndoScene();
return;
default:
return;
}
if (activeScene != null) {
Integer[] sceneOutput = getStandartSceneOutput(activeScene.getSceneID());
if (sceneOutput == null) {
sceneOutput = sceneOutputMap.get(activeScene.getSceneID());
}
if (sceneOutput != null) {
boolean outputChanged = false;
if (isShade()) {
if (isBlind() && sceneOutput[1] != slatAngle) {
logger.debug("Scene output angle: {} setted output value {}", sceneOutput[1], slatAngle);
outputChanged = true;
}
if (sceneOutput[0] != slatPosition) {
logger.debug("Scene output value: {} setted output value {}", sceneOutput[0], slatPosition);
outputChanged = true;
}
} else {
if (sceneOutput[0] != outputValue) {
outputChanged = true;
}
}
if (outputChanged) {
logger.debug("Device output from Device with dSID {} changed deactivate scene {}",
dsid.getValue(), activeScene.getID());
activeScene.deviceSceneChanged((short) -1);
lastScene = null;
activeScene = null;
}
} else {
lastScene = null;
}
}
informListenerAboutStateUpdate(deviceStateUpdate);
}
}
@Override
public void registerDeviceStateListener(DeviceStatusListener listener) {
if (listener != null) {
this.listener = listener;
listener.onDeviceAdded(this);
}
}
@Override
public DeviceStatusListener unregisterDeviceStateListener() {
activePowerRefreshPriority = Config.REFRESH_PRIORITY_NEVER;
electricMeterRefreshPriority = Config.REFRESH_PRIORITY_NEVER;
outputCurrentRefreshPriority = Config.REFRESH_PRIORITY_NEVER;
DeviceStatusListener listener = this.listener;
this.listener = null;
return listener;
}
@Override
public boolean isListenerRegisterd() {
return (listener != null);
}
private void setCachedMeterData() {
logger.debug("load cached sensor data device with dsid {}", dsid.getValue());
Integer[] cachedSensorData = this.cachedSensorMeterData.get(this.getOutputValue());
if (cachedSensorData != null) {
if (cachedSensorData[0] != null
&& !this.activePowerRefreshPriority.contains(Config.REFRESH_PRIORITY_NEVER)) {
this.activePower = cachedSensorData[0];
informListenerAboutStateUpdate(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_ACTIVE_POWER, cachedSensorData[0]));
}
if (cachedSensorData[1] != null
&& !this.outputCurrentRefreshPriority.contains(Config.REFRESH_PRIORITY_NEVER)) {
this.outputCurrent = cachedSensorData[1];
informListenerAboutStateUpdate(
new DeviceStateUpdateImpl(DeviceStateUpdate.UPDATE_OUTPUT_CURRENT, cachedSensorData[1]));
}
}
}
/**
* if an {@link DeviceStatusListener} is registered inform him about the new state otherwise do nothing.
*
* @param deviceStateUpdate
*/
private void informListenerAboutStateUpdate(DeviceStateUpdate deviceStateUpdate) {
if (listener != null) {
logger.debug("Inform listener about device state changed: type: " + deviceStateUpdate.getType()
+ ", value: " + deviceStateUpdate.getValue());
listener.onDeviceStateChanged(deviceStateUpdate);
}
}
@SuppressWarnings("null")
@Override
public void saveConfigSceneSpecificationIntoDevice(Map<String, String> propertries) {
if (propertries != null) {
String sceneSave;
for (short i = 0; i < 128; i++) {
sceneSave = propertries.get(DigitalSTROMBindingConstants.DEVICE_SCENE + i);
if (StringUtils.isNotBlank(sceneSave)) {
logger.debug("Find saved scene configuration for device with dSID {} and sceneID {}", dsid, i);
String[] sceneParm = sceneSave.replace(" ", "").split(",");
JSONDeviceSceneSpecImpl sceneSpecNew = null;
int sceneValue = -1;
int sceneAngle = -1;
for (int j = 0; j < sceneParm.length; j++) {
String[] sceneParmSplit = sceneParm[j].split(":");
switch (sceneParmSplit[0]) {
case "Scene":
sceneSpecNew = new JSONDeviceSceneSpecImpl(sceneParmSplit[1]);
break;
case "dontcare":
sceneSpecNew.setDontcare(Boolean.parseBoolean(sceneParmSplit[1]));
break;
case "localPrio":
sceneSpecNew.setLocalPrio(Boolean.parseBoolean(sceneParmSplit[1]));
break;
case "specialMode":
sceneSpecNew.setSpecialMode(Boolean.parseBoolean(sceneParmSplit[1]));
break;
case "sceneValue":
sceneValue = Integer.parseInt(sceneParmSplit[1]);
break;
case "sceneAngle":
sceneAngle = Integer.parseInt(sceneParmSplit[1]);
break;
}
}
if (sceneValue > -1) {
logger.debug("Saved sceneValue {}, sceneAngle {} for scene id {} into device with dsid {}",
sceneValue, sceneAngle, i, getDSID().getValue());
synchronized (sceneOutputMap) {
sceneOutputMap.put(i, new Integer[] { sceneValue, sceneAngle });
}
}
if (sceneSpecNew != null) {
logger.debug("Saved sceneConfig: [{}] for scene id {} into device with dsid {}",
sceneSpecNew.toString(), i, getDSID().getValue());
synchronized (sceneConfigMap) {
sceneConfigMap.put(i, sceneSpecNew);
}
}
}
}
}
}
@Override
public void setConfig(Config config) {
this.config = config;
}
}