org.eclipse.smarthome.binding.digitalstrom.internal.lib.manager.impl.DeviceStatusManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.smarthome.binding.digitalstrom.internal.lib.manager.impl.DeviceStatusManagerImpl.java

Source

/**
 * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.smarthome.binding.digitalstrom.internal.lib.manager.impl;

import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.smarthome.binding.digitalstrom.internal.lib.GeneralLibConstance;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.config.Config;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.event.EventListener;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.event.constants.EventNames;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.event.constants.EventResponseEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.event.types.EventItem;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.ConnectionListener;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.DeviceStatusListener;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.ManagerStatusListener;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.SceneStatusListener;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.TotalPowerConsumptionListener;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.stateenums.ManagerStates;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.stateenums.ManagerTypes;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.manager.ConnectionManager;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.manager.DeviceStatusManager;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.manager.SceneManager;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.manager.StructureManager;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.sensorjobexecutor.SceneReadingJobExecutor;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.sensorjobexecutor.SensorJobExecutor;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.SensorJob;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.impl.DeviceConsumptionSensorJob;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.impl.DeviceOutputValueSensorJob;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.impl.SceneConfigReadingJob;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.sensorjobexecutor.sensorjob.impl.SceneOutputValueReadingJob;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.serverconnection.DsAPI;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.serverconnection.constants.JSONApiResponseKeysEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.Circuit;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.Device;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.CachedMeteringValue;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceConstants;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.DeviceStateUpdate;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.DeviceBinarayInputEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.MeteringTypeEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.MeteringUnitsEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.OutputModeEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.constants.SensorEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DSID;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.deviceparameters.impl.DeviceStateUpdateImpl;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.impl.DeviceImpl;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.scene.InternalScene;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.scene.constants.ApartmentSceneEnum;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.scene.constants.SceneEnum;
import org.eclipse.smarthome.core.common.ThreadPoolManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

/**
 * The {@link DeviceStatusManagerImpl} is the implementation of the {@link DeviceStatusManager}.
 *
 * @author Michael Ochel - Initial contribution
 * @author Matthias Siegele - Initial contribution
 */
public class DeviceStatusManagerImpl implements DeviceStatusManager {

    private final Logger logger = LoggerFactory.getLogger(DeviceStatusManagerImpl.class);

    /**
     * Contains all supported event-types.
     */
    public static final List<String> SUPPORTED_EVENTS = Arrays.asList(EventNames.DEVICE_SENSOR_VALUE,
            EventNames.DEVICE_BINARY_INPUT_EVENT);

    private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(Config.THREADPOOL_NAME);
    private ScheduledFuture<?> pollingScheduler;

    /**
     * Query to get all {@link Device}'s with more informations than {@link DsAPI#getApartmentDevices(String)}. Can be
     * executed with {@link DsAPI#query(String, String)} or {@link DsAPI#query2(String, String)}.
     */
    public static final String GET_DETAILD_DEVICES = "/apartment/zones/zone0(*)/devices/*(*)/*(*)/*(*)";
    /**
     * Query to get the last called scenes of all groups in digitalSTROM. Can be executed with
     * {@link DsAPI#query(String, String)} or
     * {@link DsAPI#query2(String, String)}.
     */
    public static final String LAST_CALL_SCENE_QUERY = "/apartment/zones/*(*)/groups/*(*)/*(*)";

    private ConnectionManager connMan;
    private StructureManager strucMan;
    private SceneManager sceneMan;
    private DsAPI digitalSTROMClient;
    private Config config;

    private SensorJobExecutor sensorJobExecutor;
    private SceneReadingJobExecutor sceneJobExecutor;
    private EventListener eventListener;

    private final List<TrashDevice> trashDevices = new LinkedList<TrashDevice>();

    private long lastBinCheck = 0;
    private ManagerStates state = ManagerStates.STOPPED;

    private int tempConsumption = 0;
    private int tempEnergyMeter = 0;
    private int tempEnergyMeterWs = 0;

    private DeviceStatusListener deviceDiscovery;
    private TotalPowerConsumptionListener totalPowerConsumptionListener;
    private ManagerStatusListener statusListener;

    /**
     * Creates a new {@link DeviceStatusManagerImpl} through the given {@link Config} object, which has to be contains
     * all needed parameters like host address, authentication data and so on. This constructor the
     * {@link DeviceStatusManagerImpl} will be create all needed managers itself.
     *
     * @param config (must not be null)
     */
    public DeviceStatusManagerImpl(Config config) {
        init(new ConnectionManagerImpl(config), null, null, null, null);
    }

    /**
     * Creates a new {@link DeviceStatusManagerImpl}. The given fields needed to create {@link ConnectionManager}
     * through the constructor {@link ConnectionManagerImpl#ConnectionManagerImpl(String, String, String, String)}. All
     * other needed manager will be automatically created, too.
     *
     * @param hostAddress (must not be null)
     * @param user (can be null, if appToken is set)
     * @param password (can be null, if appToken is set)
     * @param appToken (can be null, if user and password is set)
     */
    public DeviceStatusManagerImpl(String hostAddress, String user, String password, String appToken) {
        init(new ConnectionManagerImpl(hostAddress, user, password, false), null, null, null, null);
    }

    /**
     * Creates a new {@link DeviceStatusManagerImpl} with the given managers. If the {@link StructureManager} or
     * {@link SceneManager} is null, they will be automatically created.
     *
     * @param connMan (must not be null)
     * @param strucMan (can be null)
     * @param sceneMan (can be null)
     */
    public DeviceStatusManagerImpl(ConnectionManager connMan, StructureManager strucMan, SceneManager sceneMan) {
        init(connMan, strucMan, sceneMan, null, null);
    }

    /**
     * Same constructor like {@link #DeviceStatusManagerImpl(ConnectionManager, StructureManager, SceneManager)}, but a
     * {@link ManagerStatusListener} can be set, too.
     *
     * @param connMan (must not be null)
     * @param strucMan (can be null)
     * @param sceneMan (can be null)
     * @param statusListener (can be null)
     * @see #DeviceStatusManagerImpl(ConnectionManager, StructureManager, SceneManager)
     */
    public DeviceStatusManagerImpl(ConnectionManager connMan, StructureManager strucMan, SceneManager sceneMan,
            ManagerStatusListener statusListener) {
        init(connMan, strucMan, sceneMan, statusListener, null);
    }

    /**
     * Same constructor like
     * {@link #DeviceStatusManagerImpl(ConnectionManager, StructureManager, SceneManager, ManagerStatusListener)}, but a
     * {@link EventListener} can be set, too.
     *
     * @param connMan (must not be null)
     * @param strucMan (can be null)
     * @param sceneMan (can be null)
     * @param statusListener (can be null)
     * @param eventListener (can be null)
     * @see #DeviceStatusManagerImpl(ConnectionManager, StructureManager, SceneManager, ManagerStatusListener)
     */
    public DeviceStatusManagerImpl(ConnectionManager connMan, StructureManager strucMan, SceneManager sceneMan,
            ManagerStatusListener statusListener, EventListener eventListener) {
        init(connMan, strucMan, sceneMan, statusListener, eventListener);
    }

    /**
     * Creates a new {@link DeviceStatusManagerImpl} with the given {@link ConnectionManager}. The
     * {@link StructureManager} and
     * {@link SceneManager} will be automatically created.
     *
     * @param connMan (must not be null)
     */
    public DeviceStatusManagerImpl(ConnectionManager connMan) {
        init(connMan, null, null, null, null);
    }

    private void init(ConnectionManager connMan, StructureManager strucMan, SceneManager sceneMan,
            ManagerStatusListener statusListener, EventListener eventListener) {
        this.connMan = connMan;
        this.digitalSTROMClient = connMan.getDigitalSTROMAPI();
        this.config = connMan.getConfig();
        if (strucMan != null) {
            this.strucMan = strucMan;
        } else {
            this.strucMan = new StructureManagerImpl();
        }
        if (sceneMan != null) {
            this.sceneMan = sceneMan;
        } else {
            this.sceneMan = new SceneManagerImpl(connMan, strucMan, statusListener);
        }
        this.statusListener = statusListener;
        this.eventListener = eventListener;
    }

    /**
     * Check and updates the {@link Device} structure, configurations and status.
     *
     * @author Michael Ochel - initial contributer
     * @author Matthias Siegele - initial contributer
     */
    private class PollingRunnable implements Runnable {
        private boolean devicesLoaded = false;
        private long nextSensorUpdate = 0;

        @Override
        public void run() {
            if (!getManagerState().equals(ManagerStates.RUNNING)) {
                logger.debug("Thread started");
                if (devicesLoaded) {
                    stateChanged(ManagerStates.RUNNING);
                } else {
                    stateChanged(ManagerStates.INITIALIZING);
                }
            }
            HashMap<DSID, Device> tempDeviceMap;
            if (strucMan.getDeviceMap() != null) {
                tempDeviceMap = (HashMap<DSID, Device>) strucMan.getDeviceMap();
            } else {
                tempDeviceMap = new HashMap<DSID, Device>();
            }

            List<Device> currentDeviceList = getDetailedDevices();

            // update the current total power consumption
            if (nextSensorUpdate <= System.currentTimeMillis()) {
                // check circuits
                List<Circuit> circuits = digitalSTROMClient.getApartmentCircuits(connMan.getSessionToken());
                for (Circuit circuit : circuits) {
                    if (strucMan.getCircuitByDSID(circuit.getDSID()) != null) {
                        if (!circuit.equals(strucMan.getCircuitByDSID(circuit.getDSID()))) {
                            strucMan.updateCircuitConfig(circuit);
                        }
                    } else {
                        strucMan.addCircuit(circuit);
                        if (deviceDiscovery != null) {
                            deviceDiscovery.onDeviceAdded(circuit);
                        }
                    }
                }
                getMeterData();
                nextSensorUpdate = System.currentTimeMillis() + config.getTotalPowerUpdateInterval();
            }

            while (!currentDeviceList.isEmpty()) {
                Device currentDevice = currentDeviceList.remove(0);
                DSID currentDeviceDSID = currentDevice.getDSID();
                Device eshDevice = tempDeviceMap.remove(currentDeviceDSID);

                if (eshDevice != null) {
                    checkDeviceConfig(currentDevice, eshDevice);

                    if (eshDevice.isPresent()) {
                        // check device state updates
                        while (!eshDevice.isDeviceUpToDate()) {
                            DeviceStateUpdate deviceStateUpdate = eshDevice.getNextDeviceUpdateState();
                            if (deviceStateUpdate != null) {
                                switch (deviceStateUpdate.getType()) {
                                case DeviceStateUpdate.OUTPUT:
                                case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
                                case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
                                    filterCommand(deviceStateUpdate, eshDevice);
                                    break;
                                case DeviceStateUpdate.UPDATE_SCENE_CONFIG:
                                case DeviceStateUpdate.UPDATE_SCENE_OUTPUT:
                                    updateSceneData(eshDevice, deviceStateUpdate);
                                    break;
                                case DeviceStateUpdate.UPDATE_OUTPUT_VALUE:
                                    if (deviceStateUpdate.getValueAsInteger() > -1) {
                                        readOutputValue(eshDevice);
                                    } else {
                                        removeSensorJob(eshDevice, deviceStateUpdate);
                                    }
                                    break;
                                default:
                                    sendComandsToDSS(eshDevice, deviceStateUpdate);
                                }
                            }
                        }
                    }

                } else {
                    logger.debug("Found new device!");
                    if (trashDevices.isEmpty()) {
                        currentDevice.setConfig(config);
                        strucMan.addDeviceToStructure(currentDevice);
                        logger.debug("trashDevices are empty, add Device with dSID {} to the deviceMap!",
                                currentDevice.getDSID());
                    } else {
                        logger.debug("Search device in trashDevices.");
                        TrashDevice foundTrashDevice = null;
                        for (TrashDevice trashDevice : trashDevices) {
                            if (trashDevice != null) {
                                if (trashDevice.getDevice().equals(currentDevice)) {
                                    foundTrashDevice = trashDevice;
                                    logger.debug(
                                            "Found device in trashDevices, add TrashDevice with dSID {} to the StructureManager!",
                                            currentDeviceDSID);
                                }
                            }
                        }
                        if (foundTrashDevice != null) {
                            trashDevices.remove(foundTrashDevice);
                            strucMan.addDeviceToStructure(foundTrashDevice.getDevice());
                        } else {
                            strucMan.addDeviceToStructure(currentDevice);
                            logger.debug(
                                    "Can't find device in trashDevices, add Device with dSID: {} to the StructureManager!",
                                    currentDeviceDSID);
                        }
                    }
                    if (deviceDiscovery != null) {
                        // only informs discovery, if the device is a output or a sensor device
                        deviceDiscovery.onDeviceAdded(currentDevice);
                        logger.debug("inform DeviceStatusListener: {} about added device with dSID {}",
                                DeviceStatusListener.DEVICE_DISCOVERY, currentDevice.getDSID().getValue());
                    } else {
                        logger.debug(
                                "The device discovery is not registrated, can't inform device discovery about found device.");
                    }
                }
            }

            if (!devicesLoaded && strucMan.getDeviceMap() != null) {
                if (!strucMan.getDeviceMap().values().isEmpty()) {
                    logger.debug("Devices loaded");
                    devicesLoaded = true;
                    setInizialStateWithLastCallScenes();
                    stateChanged(ManagerStates.RUNNING);
                } else {
                    logger.debug("No devices found");
                }
            }

            if (!sceneMan.scenesGenerated() && devicesLoaded
                    && !sceneMan.getManagerState().equals(ManagerStates.GENERATING_SCENES)) {
                logger.debug("{}", sceneMan.getManagerState());
                sceneMan.generateScenes();
            }

            for (Device device : tempDeviceMap.values()) {
                logger.debug("Found removed devices.");

                trashDevices.add(new TrashDevice(device));
                DeviceStatusListener listener = device.unregisterDeviceStatusListener();
                if (listener != null) {
                    listener.onDeviceRemoved(null);
                }
                strucMan.deleteDevice(device);
                logger.debug("Add device with dSID {} to trashDevices", device.getDSID().getValue());

                if (deviceDiscovery != null) {
                    deviceDiscovery.onDeviceRemoved(device);
                    logger.debug("inform DeviceStatusListener: {} about removed device with dSID {}",
                            DeviceStatusListener.DEVICE_DISCOVERY, device.getDSID().getValue());
                } else {
                    logger.debug(
                            "The device-Discovery is not registrated, can't inform device discovery about removed device.");
                }
            }

            if (!trashDevices.isEmpty() && (lastBinCheck + config.getBinCheckTime() < System.currentTimeMillis())) {
                for (TrashDevice trashDevice : trashDevices) {
                    if (trashDevice.isTimeToDelete(Calendar.getInstance().get(Calendar.DAY_OF_YEAR))) {
                        logger.debug("Found trashDevice that have to delete!");
                        trashDevices.remove(trashDevice);
                        logger.debug("Delete trashDevice: {}", trashDevice.getDevice().getDSID().getValue());
                    }
                }
                lastBinCheck = System.currentTimeMillis();
            }
        }

        private List<Device> getDetailedDevices() {
            List<Device> deviceList = new LinkedList<Device>();
            JsonObject result = connMan.getDigitalSTROMAPI().query2(connMan.getSessionToken(), GET_DETAILD_DEVICES);
            if (result != null && result.isJsonObject()) {
                if (result.getAsJsonObject().get(GeneralLibConstance.QUERY_BROADCAST_ZONE_STRING).isJsonObject()) {
                    result = result.getAsJsonObject().get(GeneralLibConstance.QUERY_BROADCAST_ZONE_STRING)
                            .getAsJsonObject();
                    for (Entry<String, JsonElement> entry : result.entrySet()) {
                        if (!(entry.getKey().equals(JSONApiResponseKeysEnum.ZONE_ID.getKey())
                                && entry.getKey().equals(JSONApiResponseKeysEnum.NAME.getKey()))
                                && entry.getValue().isJsonObject()) {
                            deviceList.add(new DeviceImpl(entry.getValue().getAsJsonObject()));
                        }
                    }
                }
            }
            return deviceList;
        }

        private void filterCommand(DeviceStateUpdate deviceStateUpdate, Device device) {
            String stateUpdateType = deviceStateUpdate.getType();
            short newAngle = 0;
            if (stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_INCREASE)
                    || stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_DECREASE)) {
                newAngle = device.getAnglePosition();
            }
            DeviceStateUpdate nextDeviceStateUpdate = device.getNextDeviceUpdateState();
            while (nextDeviceStateUpdate != null && nextDeviceStateUpdate.getType().equals(stateUpdateType)) {
                switch (stateUpdateType) {
                case DeviceStateUpdate.OUTPUT:
                    deviceStateUpdate = nextDeviceStateUpdate;
                    nextDeviceStateUpdate = device.getNextDeviceUpdateState();
                    break;
                case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
                    if (deviceStateUpdate.getValueAsInteger() == 1) {
                        newAngle = (short) (newAngle + DeviceConstants.ANGLE_STEP_SLAT);
                    }
                    break;
                case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
                    if (deviceStateUpdate.getValueAsInteger() == 1) {
                        newAngle = (short) (newAngle - DeviceConstants.ANGLE_STEP_SLAT);
                    }
                    break;
                }
            }
            if (stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_INCREASE)
                    || stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_DECREASE)) {
                if (newAngle > device.getMaxSlatAngle()) {
                    newAngle = (short) device.getMaxSlatAngle();
                }
                if (newAngle < device.getMinSlatAngle()) {
                    newAngle = (short) device.getMinSlatAngle();
                }
                if (!(stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_INCREASE)
                        && checkAngleIsMinMax(device) == 1)
                        || !(stateUpdateType.equals(DeviceStateUpdate.SLAT_ANGLE_DECREASE)
                                && checkAngleIsMinMax(device) == 0)) {
                    deviceStateUpdate = new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, newAngle);
                }
            }
            sendComandsToDSS(device, deviceStateUpdate);
            if (nextDeviceStateUpdate != null) {
                if (deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_CONFIG
                        || deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_OUTPUT) {
                    updateSceneData(device, deviceStateUpdate);
                } else {
                    sendComandsToDSS(device, deviceStateUpdate);
                }
            }
        }

    };

    private void removeSensorJob(Device eshDevice, DeviceStateUpdate deviceStateUpdate) {
        switch (deviceStateUpdate.getType()) {
        case DeviceStateUpdate.UPDATE_SCENE_CONFIG:
            if (sceneJobExecutor != null) {
                sceneJobExecutor.removeSensorJob(eshDevice,
                        SceneConfigReadingJob.getID(eshDevice, deviceStateUpdate.getSceneId()));
            }
            break;
        case DeviceStateUpdate.UPDATE_SCENE_OUTPUT:
            if (sceneJobExecutor != null) {
                sceneJobExecutor.removeSensorJob(eshDevice,
                        SceneOutputValueReadingJob.getID(eshDevice, deviceStateUpdate.getSceneId()));
            }
            break;
        case DeviceStateUpdate.UPDATE_OUTPUT_VALUE:
            if (sensorJobExecutor != null) {
                sensorJobExecutor.removeSensorJob(eshDevice, DeviceOutputValueSensorJob.getID(eshDevice));
            }
            break;
        }
        if (deviceStateUpdate.isSensorUpdateType()) {
            if (sensorJobExecutor != null) {
                logger.debug("remove SensorJob with ID: {}",
                        DeviceConsumptionSensorJob.getID(eshDevice, deviceStateUpdate.getTypeAsSensorEnum()));
                sensorJobExecutor.removeSensorJob(eshDevice,
                        DeviceConsumptionSensorJob.getID(eshDevice, deviceStateUpdate.getTypeAsSensorEnum()));
            }
        }
    }

    @Override
    public ManagerTypes getManagerType() {
        return ManagerTypes.DEVICE_STATUS_MANAGER;
    }

    @Override
    public synchronized ManagerStates getManagerState() {
        return state;
    }

    private synchronized void stateChanged(ManagerStates state) {
        if (statusListener != null) {
            this.state = state;
            statusListener.onStatusChanged(ManagerTypes.DEVICE_STATUS_MANAGER, state);
        }
    }

    @Override
    public synchronized void start() {
        logger.debug("start DeviceStatusManager");
        if (pollingScheduler == null || pollingScheduler.isCancelled()) {
            pollingScheduler = scheduler.scheduleWithFixedDelay(new PollingRunnable(), 0,
                    config.getPollingFrequency(), TimeUnit.MILLISECONDS);
            logger.debug("start pollingScheduler");
        }
        sceneMan.start();
        if (sceneJobExecutor != null) {
            this.sceneJobExecutor.startExecutor();
        }

        if (sensorJobExecutor != null) {
            this.sensorJobExecutor.startExecutor();
        }
        if (eventListener != null) {
            eventListener.addEventHandler(this);
        } else {
            eventListener = new EventListener(connMan, this);
            eventListener.start();
        }
    }

    @Override
    public synchronized void stop() {
        logger.debug("stop DeviceStatusManager");
        stateChanged(ManagerStates.STOPPED);
        if (sceneMan != null) {
            sceneMan.stop();
        }
        if (pollingScheduler != null && !pollingScheduler.isCancelled()) {
            pollingScheduler.cancel(true);
            pollingScheduler = null;
            logger.debug("stop pollingScheduler");
        }
        if (sceneJobExecutor != null) {
            this.sceneJobExecutor.shutdown();
        }
        if (sensorJobExecutor != null) {
            this.sensorJobExecutor.shutdown();
        }
        if (eventListener != null) {
            eventListener.removeEventHandler(this);
        }
    }

    /**
     * The {@link TrashDevice} saves not present {@link Device}'s, but at this point not deleted from the
     * digitalSTROM-System, temporary to get back the configuration of the {@link Device}'s faster.
     *
     * @author Michael Ochel - Initial contribution
     * @author Matthias Siegele - Initial contribution
     */
    private class TrashDevice {
        private final Device device;
        private final int timestamp;

        /**
         * Creates a new {@link TrashDevice}.
         *
         * @param device to put in {@link TrashDevice}
         */
        public TrashDevice(Device device) {
            this.device = device;
            this.timestamp = Calendar.getInstance().get(Calendar.DAY_OF_YEAR);
        }

        /**
         * Returns the saved {@link Device}.
         *
         * @return device
         */
        public Device getDevice() {
            return device;
        }

        /**
         * Returns true if the time for the {@link TrashDevice} is over and it can be deleted.
         *
         * @param dayOfYear day of the current year
         * @return true = time to delete | false = not time to delete
         */
        public boolean isTimeToDelete(int dayOfYear) {
            return this.timestamp + config.getTrashDeviceDeleteTime() <= dayOfYear;
        }

        @Override
        public boolean equals(Object object) {
            return object instanceof TrashDevice
                    ? this.device.getDSID().equals(((TrashDevice) object).getDevice().getDSID())
                    : false;
        }
    }

    private void checkDeviceConfig(Device newDevice, Device internalDevice) {
        if (newDevice == null || internalDevice == null) {
            return;
        }
        // check device availability has changed and informs the deviceStatusListener about the change.
        // NOTE:
        // The device is not availability for the digitalSTROM-Server, it has not been deleted and therefore it is set
        // to
        // OFFLINE.
        // An alternate algorithm is responsible for deletion.
        if (newDevice.isPresent() != internalDevice.isPresent()) {
            internalDevice.setIsPresent(newDevice.isPresent());
        }
        if (newDevice.getMeterDSID() != null && !newDevice.getMeterDSID().equals(internalDevice.getMeterDSID())) {
            internalDevice.setMeterDSID(newDevice.getMeterDSID().getValue());
        }
        if (newDevice.getFunctionalColorGroup() != null
                && !newDevice.getFunctionalColorGroup().equals(internalDevice.getFunctionalColorGroup())) {
            internalDevice.setFunctionalColorGroup(newDevice.getFunctionalColorGroup());
        }
        if (newDevice.getName() != null && !newDevice.getName().equals(internalDevice.getName())) {
            internalDevice.setName(newDevice.getName());
        }
        if (newDevice.getOutputMode() != null
                && !newDevice.getOutputMode().equals(internalDevice.getOutputMode())) {
            if (deviceDiscovery != null) {
                if (OutputModeEnum.DISABLED.equals(internalDevice.getOutputMode())
                        || OutputModeEnum.outputModeIsTemperationControlled(internalDevice.getOutputMode())) {
                    deviceDiscovery.onDeviceAdded(newDevice);
                }
                if (OutputModeEnum.DISABLED.equals(newDevice.getOutputMode())
                        || OutputModeEnum.outputModeIsTemperationControlled(newDevice.getOutputMode())) {
                    deviceDiscovery.onDeviceRemoved(newDevice);
                }
            }
            internalDevice.setOutputMode(newDevice.getOutputMode());
        }
        if (!newDevice.getBinaryInputs().equals(internalDevice.getBinaryInputs())) {
            internalDevice.setBinaryInputs(newDevice.getBinaryInputs());
        }
        strucMan.updateDevice(newDevice);
    }

    private long lastSceneCall = 0;
    private long sleepTime = 0;

    @Override
    public synchronized void sendSceneComandsToDSS(InternalScene scene, boolean call_undo) {
        if (scene != null) {
            if (lastSceneCall + 1000 > System.currentTimeMillis()) {
                sleepTime = System.currentTimeMillis() - lastSceneCall;
                try {
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                    logger.debug("An InterruptedException occurred", e);
                }
            }
            lastSceneCall = System.currentTimeMillis();
            boolean requestSuccessful = false;
            if (scene.getZoneID() == 0) {
                if (call_undo) {
                    logger.debug("{} {} {}", scene.getGroupID(), scene.getSceneID(),
                            ApartmentSceneEnum.getApartmentScene(scene.getSceneID()));
                    requestSuccessful = this.digitalSTROMClient.callApartmentScene(connMan.getSessionToken(),
                            scene.getGroupID(), null, ApartmentSceneEnum.getApartmentScene(scene.getSceneID()),
                            false);
                } else {
                    requestSuccessful = this.digitalSTROMClient.undoApartmentScene(connMan.getSessionToken(),
                            scene.getGroupID(), null, ApartmentSceneEnum.getApartmentScene(scene.getSceneID()));
                }
            } else {
                if (call_undo) {
                    requestSuccessful = this.digitalSTROMClient.callZoneScene(connMan.getSessionToken(),
                            scene.getZoneID(), null, scene.getGroupID(), null,
                            SceneEnum.getScene(scene.getSceneID()), false);
                } else {
                    requestSuccessful = this.digitalSTROMClient.undoZoneScene(connMan.getSessionToken(),
                            scene.getZoneID(), null, scene.getGroupID(), null,
                            SceneEnum.getScene(scene.getSceneID()));
                }
            }

            logger.debug("Was the scene call succsessful?: {}", requestSuccessful);
            if (requestSuccessful) {
                this.sceneMan.addEcho(scene.getID());
                if (call_undo) {
                    scene.activateScene();
                } else {
                    scene.deactivateScene();
                }
            }
        }
    }

    @Override
    public synchronized void sendStopComandsToDSS(final Device device) {
        scheduler.execute(new Runnable() {

            @Override
            public void run() {
                if (digitalSTROMClient.callDeviceScene(connMan.getSessionToken(), device.getDSID(), null, null,
                        SceneEnum.STOP, true)) {
                    sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.STOP.getSceneNumber());
                    readOutputValue(device);
                }
            }
        });
    }

    private void readOutputValue(Device device) {
        short outputIndex = DeviceConstants.DEVICE_SENSOR_OUTPUT;
        if (device.isShade()) {
            outputIndex = DeviceConstants.DEVICE_SENSOR_SLAT_POSITION_OUTPUT;
        }

        int outputValue = this.digitalSTROMClient.getDeviceOutputValue(connMan.getSessionToken(), device.getDSID(),
                null, null, outputIndex);
        if (outputValue != -1) {
            if (!device.isShade()) {
                device.updateInternalDeviceState(new DeviceStateUpdateImpl(DeviceStateUpdate.OUTPUT, outputValue));
            } else {
                device.updateInternalDeviceState(
                        new DeviceStateUpdateImpl(DeviceStateUpdate.SLATPOSITION, outputValue));
                if (device.isBlind()) {
                    outputValue = this.digitalSTROMClient.getDeviceOutputValue(connMan.getSessionToken(),
                            device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_ANGLE_OUTPUT);
                    device.updateInternalDeviceState(
                            new DeviceStateUpdateImpl(DeviceStateUpdate.SLAT_ANGLE, outputValue));
                }
            }
        }
    }

    /**
     * Updates the {@link Device} status of the given {@link Device} with handling outstanding commands, which are saved
     * as {@link DeviceStateUpdate}'s.
     *
     * @param eshDevice to update
     */
    public synchronized void updateDevice(Device eshDevice) {
        logger.debug("Check device updates");
        // check device state updates
        while (!eshDevice.isDeviceUpToDate()) {
            DeviceStateUpdate deviceStateUpdate = eshDevice.getNextDeviceUpdateState();
            if (deviceStateUpdate != null) {
                if (deviceStateUpdate.getType() != DeviceStateUpdate.OUTPUT) {
                    if (deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_CONFIG
                            || deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_OUTPUT) {
                        updateSceneData(eshDevice, deviceStateUpdate);
                    } else {
                        sendComandsToDSS(eshDevice, deviceStateUpdate);
                    }
                } else {
                    DeviceStateUpdate nextDeviceStateUpdate = eshDevice.getNextDeviceUpdateState();
                    while (nextDeviceStateUpdate != null
                            && nextDeviceStateUpdate.getType() == DeviceStateUpdate.OUTPUT) {
                        deviceStateUpdate = nextDeviceStateUpdate;
                        nextDeviceStateUpdate = eshDevice.getNextDeviceUpdateState();
                    }
                    sendComandsToDSS(eshDevice, deviceStateUpdate);
                    if (nextDeviceStateUpdate != null) {
                        if (deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_CONFIG
                                || deviceStateUpdate.getType() == DeviceStateUpdate.UPDATE_SCENE_OUTPUT) {
                            updateSceneData(eshDevice, deviceStateUpdate);
                        } else {
                            sendComandsToDSS(eshDevice, deviceStateUpdate);
                        }
                    }
                }
            }
        }
    }

    /**
     * Checks the output value of a {@link Device} and return 0, if the output value or slat position is min and 1, if
     * the output value or slat position is max, otherwise it returns -1.
     *
     * @param device
     * @return 0 = output value is min, 1 device value is min, otherwise -1
     */
    private short checkIsAllreadyMinMax(Device device) {
        if (device.isShade()) {
            if (device.getSlatPosition() == device.getMaxSlatPosition()) {
                if (device.isBlind()) {
                    if (device.getAnglePosition() == device.getMaxSlatAngle()) {
                        return 1;
                    } else {
                        return -1;
                    }
                }
                return 1;
            }
            if (device.getSlatPosition() == device.getMinSlatPosition()) {
                if (device.isBlind()) {
                    if (device.getAnglePosition() == device.getMinSlatAngle()) {
                        return 0;
                    } else {
                        return -1;
                    }
                }
                return 0;
            }
        } else {
            if (device.getOutputValue() == device.getMaxOutputValue()) {
                return 1;
            }
            if (device.getOutputValue() == device.getMinOutputValue() || device.getOutputValue() <= 0) {
                return 0;
            }
        }
        return -1;
    }

    /**
     * Checks the angle value of a {@link Device} and return 0, if the angle value is min and 1, if the angle value is
     * max, otherwise it returns -1.
     *
     * @param device
     * @return 0 = angle value is min, 1 angle value is min, otherwise -1
     */
    private short checkAngleIsMinMax(Device device) {
        if (device.getAnglePosition() == device.getMaxSlatAngle()) {
            return 1;
        }
        if (device.getAnglePosition() == device.getMinSlatAngle()) {
            return 1;
        }
        return -1;
    }

    @Override
    public synchronized void sendComandsToDSS(Device device, DeviceStateUpdate deviceStateUpdate) {
        boolean requestSuccsessful = false;
        boolean commandHasNoEffect = false;
        if (deviceStateUpdate != null) {
            if (deviceStateUpdate.isSensorUpdateType()) {
                SensorEnum sensorType = deviceStateUpdate.getTypeAsSensorEnum();
                if (deviceStateUpdate.getValueAsInteger() == 0) {
                    updateSensorData(new DeviceConsumptionSensorJob(device, sensorType),
                            device.getPowerSensorRefreshPriority(sensorType));
                    return;
                } else if (deviceStateUpdate.getValueAsInteger() < 0) {
                    removeSensorJob(device, deviceStateUpdate);
                    return;
                } else {
                    int consumption = this.digitalSTROMClient.getDeviceSensorValue(connMan.getSessionToken(),
                            device.getDSID(), null, null, device.getSensorIndex(sensorType));
                    if (consumption >= 0) {
                        device.setDeviceSensorDsValueBySensorJob(sensorType, consumption);
                        requestSuccsessful = true;
                    }
                }
            } else {
                switch (deviceStateUpdate.getType()) {
                case DeviceStateUpdate.OUTPUT_DECREASE:
                case DeviceStateUpdate.SLAT_DECREASE:
                    if (checkIsAllreadyMinMax(device) != 0) {
                        requestSuccsessful = digitalSTROMClient.decreaseValue(connMan.getSessionToken(),
                                device.getDSID(), null, null);
                        if (requestSuccsessful) {
                            sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.DECREMENT.getSceneNumber());
                        }
                    } else {
                        commandHasNoEffect = true;
                    }
                    break;
                case DeviceStateUpdate.OUTPUT_INCREASE:
                case DeviceStateUpdate.SLAT_INCREASE:
                    if (checkIsAllreadyMinMax(device) != 1) {
                        requestSuccsessful = digitalSTROMClient.increaseValue(connMan.getSessionToken(),
                                device.getDSID(), null, null);
                        if (requestSuccsessful) {
                            sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.INCREMENT.getSceneNumber());
                        }
                    } else {
                        commandHasNoEffect = true;
                    }
                    break;
                case DeviceStateUpdate.OUTPUT:
                    if (device.getOutputValue() != deviceStateUpdate.getValueAsInteger()) {
                        requestSuccsessful = digitalSTROMClient.setDeviceValue(connMan.getSessionToken(),
                                device.getDSID(), null, null, deviceStateUpdate.getValueAsInteger());
                    } else {
                        commandHasNoEffect = true;
                    }
                    break;
                case DeviceStateUpdate.OPEN_CLOSE:
                case DeviceStateUpdate.ON_OFF:
                    if (deviceStateUpdate.getValueAsInteger() > 0) {
                        if (checkIsAllreadyMinMax(device) != 1) {
                            requestSuccsessful = digitalSTROMClient.turnDeviceOn(connMan.getSessionToken(),
                                    device.getDSID(), null, null);
                            if (requestSuccsessful) {
                                sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MAXIMUM.getSceneNumber());
                            }
                        } else {
                            commandHasNoEffect = true;
                        }
                    } else {
                        if (checkIsAllreadyMinMax(device) != 0) {
                            requestSuccsessful = digitalSTROMClient.turnDeviceOff(connMan.getSessionToken(),
                                    device.getDSID(), null, null);
                            if (requestSuccsessful) {
                                sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MINIMUM.getSceneNumber());
                            }
                        } else {
                            commandHasNoEffect = true;
                        }
                    }
                    break;
                case DeviceStateUpdate.SLATPOSITION:
                    if (device.getSlatPosition() != deviceStateUpdate.getValueAsInteger()) {
                        requestSuccsessful = digitalSTROMClient.setDeviceOutputValue(connMan.getSessionToken(),
                                device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_POSITION_OUTPUT,
                                deviceStateUpdate.getValueAsInteger());
                    } else {
                        commandHasNoEffect = true;
                    }
                    break;
                case DeviceStateUpdate.SLAT_STOP:
                    this.sendStopComandsToDSS(device);
                    break;
                case DeviceStateUpdate.SLAT_MOVE:
                    if (deviceStateUpdate.getValueAsInteger() > 0) {
                        requestSuccsessful = digitalSTROMClient.turnDeviceOn(connMan.getSessionToken(),
                                device.getDSID(), null, null);
                        if (requestSuccsessful) {
                            sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MAXIMUM.getSceneNumber());
                        }
                    } else {
                        requestSuccsessful = digitalSTROMClient.turnDeviceOff(connMan.getSessionToken(),
                                device.getDSID(), null, null);
                        if (requestSuccsessful) {
                            sceneMan.addEcho(device.getDSID().getValue(), SceneEnum.MINIMUM.getSceneNumber());
                        }
                        if (sensorJobExecutor != null) {
                            sensorJobExecutor.removeSensorJobs(device);
                        }
                    }
                    break;
                case DeviceStateUpdate.UPDATE_CALL_SCENE:
                    if (SceneEnum.getScene((short) deviceStateUpdate.getValue()) != null) {
                        requestSuccsessful = digitalSTROMClient.callDeviceScene(connMan.getSessionToken(),
                                device.getDSID(), null, null,
                                SceneEnum.getScene((short) deviceStateUpdate.getValue()), true);
                    }
                    break;
                case DeviceStateUpdate.UPDATE_UNDO_SCENE:
                    if (SceneEnum.getScene((short) deviceStateUpdate.getValue()) != null) {
                        requestSuccsessful = digitalSTROMClient.undoDeviceScene(connMan.getSessionToken(),
                                device.getDSID(), null, null,
                                SceneEnum.getScene((short) deviceStateUpdate.getValue()));
                    }
                    break;
                case DeviceStateUpdate.SLAT_ANGLE_DECREASE:
                    // By UPDATE_SLAT_ANGLE_DECREASE, UPDATE_SLAT_ANGLE_INCREASE with value unequal 1 which will
                    // handled in the pollingRunnable and UPDATE_OPEN_CLOSE_ANGLE the value will be set without
                    // checking, because it was triggered by setting the slat position.
                    requestSuccsessful = true;
                    break;
                case DeviceStateUpdate.SLAT_ANGLE_INCREASE:
                    requestSuccsessful = true;
                    break;
                case DeviceStateUpdate.OPEN_CLOSE_ANGLE:
                    requestSuccsessful = true;
                    break;
                case DeviceStateUpdate.SLAT_ANGLE:
                    if (device.getAnglePosition() != deviceStateUpdate.getValueAsInteger()) {
                        requestSuccsessful = digitalSTROMClient.setDeviceOutputValue(connMan.getSessionToken(),
                                device.getDSID(), null, null, DeviceConstants.DEVICE_SENSOR_SLAT_ANGLE_OUTPUT,
                                deviceStateUpdate.getValueAsInteger());
                    } else {
                        commandHasNoEffect = true;
                    }
                    break;
                case DeviceStateUpdate.REFRESH_OUTPUT:
                    readOutputValue(device);
                    logger.debug("Inizalize output value reading for device with dSID {}.",
                            device.getDSID().getValue());
                    return;
                default:
                    return;
                }
            }
            if (commandHasNoEffect) {
                logger.debug("Command {} for device with dSID {} not send to dSS, because it has no effect!",
                        deviceStateUpdate.getType(), device.getDSID().getValue());
                return;
            }
            if (requestSuccsessful) {
                logger.debug("Send {} command to dSS and updateInternalDeviceState for device with dSID {}.",
                        deviceStateUpdate.getType(), device.getDSID().getValue());
                device.updateInternalDeviceState(deviceStateUpdate);
            } else {
                logger.debug("Can't send {} command for device with dSID {} to dSS!", deviceStateUpdate.getType(),
                        device.getDSID().getValue());
            }
        }
    }

    @Override
    public void updateSensorData(SensorJob sensorJob, String priority) {
        if (sensorJobExecutor == null) {
            sensorJobExecutor = new SensorJobExecutor(connMan);
            this.sensorJobExecutor.startExecutor();
        }
        if (sensorJob != null && priority != null) {
            switch (priority) {
            case Config.REFRESH_PRIORITY_HIGH:
                sensorJobExecutor.addHighPriorityJob(sensorJob);
                break;
            case Config.REFRESH_PRIORITY_MEDIUM:
                sensorJobExecutor.addMediumPriorityJob(sensorJob);
                break;
            case Config.REFRESH_PRIORITY_LOW:
                sensorJobExecutor.addLowPriorityJob(sensorJob);
                break;
            default:
                try {
                    long prio = Long.parseLong(priority);
                    sensorJobExecutor.addPriorityJob(sensorJob, prio);
                } catch (NumberFormatException e) {
                    logger.debug("Sensor data update priority do not exist! Please check the input!");
                    return;
                }
            }
            logger.debug("Add new sensorJob {} with priority: {} to sensorJobExecuter", sensorJob.toString(),
                    priority);
        }
    }

    @Override
    public void updateSceneData(Device device, DeviceStateUpdate deviceStateUpdate) {
        if (sceneJobExecutor == null) {
            sceneJobExecutor = new SceneReadingJobExecutor(connMan);
            this.sceneJobExecutor.startExecutor();
        }

        if (deviceStateUpdate != null) {
            if (deviceStateUpdate.getScenePriority() > -1) {
                if (deviceStateUpdate.getType().equals(DeviceStateUpdate.UPDATE_SCENE_OUTPUT)) {
                    sceneJobExecutor.addPriorityJob(
                            new SceneOutputValueReadingJob(device, deviceStateUpdate.getSceneId()),
                            deviceStateUpdate.getScenePriority().longValue());
                } else {
                    sceneJobExecutor.addPriorityJob(
                            new SceneConfigReadingJob(device, deviceStateUpdate.getSceneId()),
                            deviceStateUpdate.getScenePriority().longValue());
                }
                if (deviceStateUpdate.getScenePriority() == 0) {
                    updateSensorData(new DeviceOutputValueSensorJob(device), "0");
                }
                logger.debug("Add new sceneReadingJob with priority: {} to SceneReadingJobExecuter",
                        deviceStateUpdate.getScenePriority());
            } else {
                removeSensorJob(device, deviceStateUpdate);
            }
        }
    }

    @Override
    public void registerDeviceListener(DeviceStatusListener deviceListener) {
        if (deviceListener != null) {
            String id = deviceListener.getDeviceStatusListenerID();
            if (id.equals(DeviceStatusListener.DEVICE_DISCOVERY)) {
                this.deviceDiscovery = deviceListener;
                logger.debug("register Device-Discovery ");
                for (Device device : strucMan.getDeviceMap().values()) {
                    deviceDiscovery.onDeviceAdded(device);
                }
                for (Circuit circuit : strucMan.getCircuitMap().values()) {
                    deviceDiscovery.onDeviceAdded(circuit);
                }
            } else {
                Device intDevice = strucMan.getDeviceByDSID(deviceListener.getDeviceStatusListenerID());
                if (intDevice != null) {
                    logger.debug("register DeviceListener with id: {} to Device ", id);
                    intDevice.registerDeviceStatusListener(deviceListener);
                } else {
                    Circuit intCircuit = strucMan
                            .getCircuitByDSID(new DSID(deviceListener.getDeviceStatusListenerID()));
                    if (intCircuit != null) {
                        logger.debug("register DeviceListener with id: {} to Circuit ", id);
                        intCircuit.registerDeviceStatusListener(deviceListener);
                    } else {
                        deviceListener.onDeviceRemoved(null);
                    }
                }
            }
        }
    }

    @Override
    public void unregisterDeviceListener(DeviceStatusListener deviceListener) {
        if (deviceListener != null) {
            String id = deviceListener.getDeviceStatusListenerID();
            logger.debug("unregister DeviceListener with id: {}", id);
            if (id.equals(DeviceStatusListener.DEVICE_DISCOVERY)) {
                this.deviceDiscovery = null;
            } else {
                Device intDevice = strucMan.getDeviceByDSID(deviceListener.getDeviceStatusListenerID());
                if (intDevice != null) {
                    intDevice.unregisterDeviceStatusListener();
                } else {
                    Circuit intCircuit = strucMan
                            .getCircuitByDSID(new DSID(deviceListener.getDeviceStatusListenerID()));
                    if (intCircuit != null) {
                        intCircuit.unregisterDeviceStatusListener();
                        if (deviceDiscovery != null) {
                            deviceDiscovery.onDeviceAdded(intCircuit);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void removeDevice(String dSID) {
        Device intDevice = strucMan.getDeviceByDSID(dSID);
        if (intDevice != null) {
            strucMan.deleteDevice(intDevice);
            trashDevices.add(new TrashDevice(intDevice));
        }
    }

    @Override
    public void registerTotalPowerConsumptionListener(TotalPowerConsumptionListener totalPowerConsumptionListener) {
        this.totalPowerConsumptionListener = totalPowerConsumptionListener;
    }

    @Override
    public void unregisterTotalPowerConsumptionListener() {
        this.totalPowerConsumptionListener = null;
    }

    @Override
    public void registerSceneListener(SceneStatusListener sceneListener) {
        this.sceneMan.registerSceneListener(sceneListener);
    }

    @Override
    public void unregisterSceneListener(SceneStatusListener sceneListener) {
        this.sceneMan.unregisterSceneListener(sceneListener);
    }

    @Override
    public void registerStatusListener(ManagerStatusListener statusListener) {
        this.statusListener = statusListener;
        this.sceneMan.registerStatusListener(statusListener);
    }

    @Override
    public void unregisterStatusListener() {
        this.statusListener = null;
        this.sceneMan.unregisterStatusListener();
    }

    @Override
    public void registerConnectionListener(ConnectionListener connectionListener) {
        this.connMan.registerConnectionListener(connectionListener);
    }

    @Override
    public void unregisterConnectionListener() {
        this.connMan.unregisterConnectionListener();
    }

    @Override
    public int getTotalPowerConsumption() {
        List<CachedMeteringValue> cachedConsumptionMeteringValues = digitalSTROMClient
                .getLatest(connMan.getSessionToken(), MeteringTypeEnum.CONSUMPTION, DsAPI.ALL_METERS, null);
        if (cachedConsumptionMeteringValues != null) {
            tempConsumption = 0;
            for (CachedMeteringValue value : cachedConsumptionMeteringValues) {
                tempConsumption += value.getValue();
                if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
                    strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
                }
            }
        }
        return tempConsumption;
    }

    private void getMeterData() {
        int val = getTotalPowerConsumption();
        if (totalPowerConsumptionListener != null) {
            totalPowerConsumptionListener.onTotalPowerConsumptionChanged(val);
        }
        val = getTotalEnergyMeterValue();
        if (totalPowerConsumptionListener != null) {
            totalPowerConsumptionListener.onEnergyMeterValueChanged(val);
        }

    }

    @Override
    public int getTotalEnergyMeterValue() {
        List<CachedMeteringValue> cachedEnergyMeteringValues = digitalSTROMClient
                .getLatest(connMan.getSessionToken(), MeteringTypeEnum.ENERGY, DsAPI.ALL_METERS, null);
        if (cachedEnergyMeteringValues != null) {
            tempEnergyMeter = 0;
            for (CachedMeteringValue value : cachedEnergyMeteringValues) {
                tempEnergyMeter += value.getValue();
                if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
                    strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
                }
            }
        }
        return tempEnergyMeter;
    }

    @Override
    public int getTotalEnergyMeterWsValue() {
        List<CachedMeteringValue> cachedEnergyMeteringValues = digitalSTROMClient.getLatest(
                connMan.getSessionToken(), MeteringTypeEnum.ENERGY, DsAPI.ALL_METERS, MeteringUnitsEnum.WS);
        if (cachedEnergyMeteringValues != null) {
            tempEnergyMeterWs = 0;
            for (CachedMeteringValue value : cachedEnergyMeteringValues) {
                tempEnergyMeterWs += value.getValue();
                if (strucMan.getCircuitByDSID(value.getDsid()) != null) {
                    strucMan.getCircuitByDSID(value.getDsid()).addMeteringValue(value);
                }
            }
        }
        return tempEnergyMeterWs;
    }

    private void setInizialStateWithLastCallScenes() {
        if (sceneMan == null) {
            return;
        }
        JsonObject response = connMan.getDigitalSTROMAPI().query2(connMan.getSessionToken(), LAST_CALL_SCENE_QUERY);
        if (!response.isJsonObject()) {
            return;
        }
        for (Entry<String, JsonElement> entry : response.entrySet()) {
            if (!entry.getValue().isJsonObject()) {
                continue;
            }
            JsonObject zone = entry.getValue().getAsJsonObject();
            int zoneID = -1;
            short groupID = -1;
            short sceneID = -1;
            if (zone.get(JSONApiResponseKeysEnum.ZONE_ID.getKey()) != null) {
                zoneID = zone.get(JSONApiResponseKeysEnum.ZONE_ID.getKey()).getAsInt();
            }
            for (Entry<String, JsonElement> groupEntry : zone.entrySet()) {
                if (groupEntry.getKey().startsWith("group") && groupEntry.getValue().isJsonObject()) {
                    JsonObject group = groupEntry.getValue().getAsJsonObject();
                    if (group.get(JSONApiResponseKeysEnum.DEVICES.getKey()) != null) {
                        if (group.get(JSONApiResponseKeysEnum.GROUP.getKey()) != null) {
                            groupID = group.get(JSONApiResponseKeysEnum.GROUP.getKey()).getAsShort();
                        }
                        if (group.get(JSONApiResponseKeysEnum.LAST_CALL_SCENE.getKey()) != null) {
                            sceneID = group.get(JSONApiResponseKeysEnum.LAST_CALL_SCENE.getKey()).getAsShort();
                        }
                        if (zoneID > -1 && groupID > -1 && sceneID > -1) {
                            logger.debug("initial state, call scene {}-{}-{}", zoneID, groupID, sceneID);
                            sceneMan.callInternalSceneWithoutDiscovery(zoneID, groupID, sceneID);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void handleEvent(EventItem eventItem) {
        if (EventNames.DEVICE_SENSOR_VALUE.equals(eventItem.getName())
                || EventNames.DEVICE_BINARY_INPUT_EVENT.equals(eventItem.getName())) {
            logger.debug("Detect {} eventItem = {}", eventItem.getName(), eventItem.toString());
            Device dev = getDeviceOfEvent(eventItem);
            if (dev != null) {
                if (EventNames.DEVICE_SENSOR_VALUE.equals(eventItem.getName())) {
                    dev.setDeviceSensorByEvent(eventItem);
                } else {
                    DeviceBinarayInputEnum binaryInputType = DeviceBinarayInputEnum.getdeviceBinarayInput(
                            Short.parseShort(eventItem.getProperties().get(EventResponseEnum.INPUT_TYPE)));
                    Short newState = Short.parseShort(eventItem.getProperties().get(EventResponseEnum.INPUT_STATE));
                    if (binaryInputType != null) {
                        dev.setBinaryInputState(binaryInputType, newState);
                    }
                }
            }
        }
    }

    private Device getDeviceOfEvent(EventItem eventItem) {
        if (eventItem.getSource().get(EventResponseEnum.DSID) != null) {
            String dSID = eventItem.getSource().get(EventResponseEnum.DSID);
            Device dev = strucMan.getDeviceByDSID(dSID);
            if (dev == null) {
                dev = strucMan.getDeviceByDSUID(dSID);
            }
            return dev;
        }
        return null;
    }

    @Override
    public List<String> getSupportedEvents() {
        return SUPPORTED_EVENTS;
    }

    @Override
    public boolean supportsEvent(String eventName) {
        return SUPPORTED_EVENTS.contains(eventName);
    }

    @Override
    public String getUID() {
        return getClass().getName();
    }

    @Override
    public void setEventListener(EventListener eventListener) {
        if (this.eventListener != null) {
            this.eventListener.removeEventHandler(this);
        }
        this.eventListener = eventListener;
    }

    @Override
    public void unsetEventListener(EventListener eventListener) {
        if (this.eventListener != null) {
            this.eventListener.removeEventHandler(this);
        }
        this.eventListener = null;
    }
}