org.openhab.io.imperihome.internal.processor.ItemProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.io.imperihome.internal.processor.ItemProcessor.java

Source

/**
 * Copyright (c) 2010-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.openhab.io.imperihome.internal.processor;

import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.core.items.Item;
import org.eclipse.smarthome.core.items.ItemRegistry;
import org.eclipse.smarthome.core.items.ItemRegistryChangeListener;
import org.eclipse.smarthome.core.library.types.DecimalType;
import org.eclipse.smarthome.core.library.types.HSBType;
import org.eclipse.smarthome.core.library.types.OnOffType;
import org.eclipse.smarthome.core.library.types.OpenClosedType;
import org.eclipse.smarthome.core.types.State;
import org.openhab.io.imperihome.internal.ImperiHomeConfig;
import org.openhab.io.imperihome.internal.action.ActionRegistry;
import org.openhab.io.imperihome.internal.model.device.AbstractDevice;
import org.openhab.io.imperihome.internal.model.device.AbstractNumericValueDevice;
import org.openhab.io.imperihome.internal.model.device.Co2SensorDevice;
import org.openhab.io.imperihome.internal.model.device.DeviceType;
import org.openhab.io.imperihome.internal.model.device.DimmerDevice;
import org.openhab.io.imperihome.internal.model.device.ElectricityDevice;
import org.openhab.io.imperihome.internal.model.device.GenericSensorDevice;
import org.openhab.io.imperihome.internal.model.device.HygrometryDevice;
import org.openhab.io.imperihome.internal.model.device.LockDevice;
import org.openhab.io.imperihome.internal.model.device.LuminosityDevice;
import org.openhab.io.imperihome.internal.model.device.MultiSwitchDevice;
import org.openhab.io.imperihome.internal.model.device.NoiseDevice;
import org.openhab.io.imperihome.internal.model.device.PressureDevice;
import org.openhab.io.imperihome.internal.model.device.RainDevice;
import org.openhab.io.imperihome.internal.model.device.RgbLightDevice;
import org.openhab.io.imperihome.internal.model.device.SceneDevice;
import org.openhab.io.imperihome.internal.model.device.ShutterDevice;
import org.openhab.io.imperihome.internal.model.device.SwitchDevice;
import org.openhab.io.imperihome.internal.model.device.TempHygroDevice;
import org.openhab.io.imperihome.internal.model.device.TemperatureDevice;
import org.openhab.io.imperihome.internal.model.device.ThermostatDevice;
import org.openhab.io.imperihome.internal.model.device.TrippableDevice;
import org.openhab.io.imperihome.internal.model.device.UvDevice;
import org.openhab.io.imperihome.internal.model.device.WindDevice;
import org.openhab.io.imperihome.internal.model.param.DeviceParam;
import org.openhab.io.imperihome.internal.model.param.ParamType;
import org.openhab.io.imperihome.internal.util.DigestUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Processor of openHAB Items. Parses ISS tags and creates and registers {@link AbstractDevice} implementations where
 * applicable.
 *
 * @author Pepijn de Geus - Initial contribution
 */
public class ItemProcessor implements ItemRegistryChangeListener {

    private static final String PREFIX_ISS = "iss:";

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

    private final ItemRegistry itemRegistry;
    private final DeviceRegistry deviceRegistry;
    private final ActionRegistry actionRegistry;
    private final ImperiHomeConfig config;

    public ItemProcessor(ItemRegistry itemRegistry, DeviceRegistry deviceRegistry, ActionRegistry actionRegistry,
            ImperiHomeConfig config) {
        this.itemRegistry = itemRegistry;
        this.deviceRegistry = deviceRegistry;
        this.actionRegistry = actionRegistry;
        this.config = config;

        allItemsChanged(null);
        itemRegistry.addRegistryChangeListener(this);
    }

    public void destroy() {
        itemRegistry.removeRegistryChangeListener(this);

        // Destroy all Devices (unregisters state listeners)
        synchronized (deviceRegistry) {
            for (AbstractDevice device : deviceRegistry) {
                device.destroy();
            }
            deviceRegistry.clear();
        }
    }

    private void parseItem(Item item) {
        Map<TagType, List<String>> issTags = getIssTags(item);
        if (!issTags.isEmpty()) {
            logger.debug("Found item {} with ISS tags: {}", item, issTags);

            DeviceType deviceType = getDeviceType(item, issTags);
            if (deviceType == null) {
                logger.warn("Unrecognized device type for item: {}", item);
            } else {
                AbstractDevice device = getDeviceInstance(deviceType, item);
                device.setId(getDeviceId(item));
                device.setName(getLabel(item, issTags));
                device.setInverted(isInverted(issTags));
                device.setActionRegistry(actionRegistry);

                setIcon(device, issTags);
                setDeviceRoom(device, issTags);
                setDeviceLinks(device, item, issTags);
                setMapping(device, item, issTags);
                setUnit(device, issTags);

                device.processCustomTags(issTags);

                // Set initial state
                logger.debug("Setting initial state of {} to {}", device, item.getState());
                device.stateUpdated(item, item.getState());

                logger.debug("Item parsed to device: {}", device);
                synchronized (deviceRegistry) {
                    deviceRegistry.add(device);
                }
            }
        }
    }

    private void setIcon(AbstractDevice device, Map<TagType, List<String>> issTags) {
        if (!issTags.containsKey(TagType.ICON)) {
            return;
        }

        String icon = issTags.get(TagType.ICON).get(0);
        if (!icon.toLowerCase().startsWith("http")) {
            if (StringUtils.isEmpty(config.getRootUrl())) {
                logger.error("Can't set icon; 'openhab.rootUrl' not set in configuration");
                return;
            }
            icon = config.getRootUrl() + "icon/" + icon;
        }

        device.addParam(new DeviceParam(ParamType.DEFAULT_ICON, icon));
    }

    private AbstractDevice getDeviceInstance(DeviceType deviceType, Item item) {
        switch (deviceType) {
        case SWITCH:
            return new SwitchDevice(item);
        case DIMMER:
            return new DimmerDevice(item);
        case RGB_LIGHT:
            return new RgbLightDevice(item);
        case TEMPERATURE:
            return new TemperatureDevice(item);
        case TEMP_HYGRO:
            return new TempHygroDevice(item);
        case LUMINOSITY:
            return new LuminosityDevice(item);
        case HYGROMETRY:
            return new HygrometryDevice(item);
        case CO2:
            return new Co2SensorDevice(item);
        case ELECTRICITY:
            return new ElectricityDevice(item);
        case SCENE:
            return new SceneDevice(item);
        case MULTI_SWITCH:
            return new MultiSwitchDevice(item);
        case GENERIC_SENSOR:
            return new GenericSensorDevice(item);
        case PRESSURE:
            return new PressureDevice(item);
        case UV:
            return new UvDevice(item);
        case NOISE:
            return new NoiseDevice(item);
        case RAIN:
            return new RainDevice(item);
        case WIND:
            return new WindDevice(item);
        case LOCK:
            return new LockDevice(item);
        case SHUTTER:
            return new ShutterDevice(item);
        case THERMOSTAT:
            return new ThermostatDevice(item);
        case CO2_ALERT:
        case SMOKE:
        case DOOR:
        case MOTION:
        case FLOOD:
            return new TrippableDevice(deviceType, item);
        default:
            break;
        }

        throw new IllegalArgumentException("Unknown device type: " + deviceType);
    }

    private String getLabel(Item item, Map<TagType, List<String>> issTags) {
        if (issTags.containsKey(TagType.LABEL)) {
            return issTags.get(TagType.LABEL).get(0);
        }

        if (StringUtils.isNotBlank(item.getLabel())) {
            String label = item.getLabel().trim();
            if (label.matches("\\[.*\\]$")) {
                label = label.substring(0, label.indexOf('['));
            }
            return label;
        }

        return item.getName();
    }

    private boolean isInverted(Map<TagType, List<String>> issTags) {
        return issTags.containsKey(TagType.INVERT) && BooleanUtils.toBoolean(issTags.get(TagType.INVERT).get(0));
    }

    private void setDeviceRoom(AbstractDevice device, Map<TagType, List<String>> issTags) {
        String roomName = "No room";
        if (issTags.containsKey(TagType.ROOM)) {
            roomName = issTags.get(TagType.ROOM).get(0);
        }

        device.setRoom(DigestUtil.sha1(roomName));
        device.setRoomName(roomName);
    }

    private void setDeviceLinks(AbstractDevice device, Item item, Map<TagType, List<String>> issTags) {
        if (issTags.containsKey(TagType.LINK)) {
            // Pass device registry to device for linked device lookup
            device.setDeviceRegistry(deviceRegistry);

            // Parse link tags
            for (String link : issTags.get(TagType.LINK)) {
                String[] parts = link.split(":");
                if (parts.length == 2) {
                    device.addLink(parts[0].toLowerCase().trim(), parts[1].trim());
                } else {
                    logger.error("Item has incorrect link format (should be 'iss:link:<type>:<item>'): {}", item);
                }
            }

            // Check required links
            for (String requiredLink : device.getType().getRequiredLinks()) {
                if (!device.getLinks().containsKey(requiredLink)) {
                    logger.error("Item doesn't contain required link {} for {}: {}", requiredLink,
                            device.getType().getApiString(), item);
                }
            }
        }
    }

    /**
     * Parses a mapping tag, if it exists. Format: "iss:mapping:1=Foo,2=Bar,3=Foobar".
     */
    private void setMapping(AbstractDevice device, Item item, Map<TagType, List<String>> issTags) {
        if (issTags.containsKey(TagType.MAPPING)) {
            String mapItems = issTags.get(TagType.MAPPING).get(0);

            Map<String, String> mapping = new HashMap<>();
            for (String mapItem : mapItems.split(",")) {
                String[] keyVal = mapItem.split("=", 2);
                if (keyVal.length != 2) {
                    logger.error("Invalid mapping syntax for Item {}", item);
                    return;
                }
                mapping.put(keyVal[0].trim(), keyVal[1].trim());
            }

            device.setMapping(mapping);
        }
    }

    /**
     * Parses the unit tag, if it exists. Format: "iss:unit:C".
     */
    private void setUnit(AbstractDevice device, Map<TagType, List<String>> issTags) {
        if (issTags.containsKey(TagType.UNIT)) {
            if (!(device instanceof AbstractNumericValueDevice)) {
                logger.warn("Unit tag is not supported for device {}", device);
                return;
            }

            ((AbstractNumericValueDevice) device).setUnit(issTags.get(TagType.UNIT).get(0));
        }
    }

    /**
     * Determines the Device type for the given Item. Uses the 'type' tag first, tries to auto-detect the type if no
     * such tag exists.
     */
    private DeviceType getDeviceType(Item item, Map<TagType, List<String>> issTags) {
        if (issTags.containsKey(TagType.TYPE)) {
            return DeviceType.forApiString(issTags.get(TagType.TYPE).get(0));
        }

        List<Class<? extends State>> acceptedDataTypes = item.getAcceptedDataTypes();
        String name = item.getName().toLowerCase();

        if (acceptedDataTypes.contains(DecimalType.class)) {
            if (name.contains("tempe")) {
                return DeviceType.TEMPERATURE;
            } else if (name.contains("lumi")) {
                return DeviceType.LUMINOSITY;
            } else if (name.contains("hygro")) {
                return DeviceType.HYGROMETRY;
            } else if (name.contains("wind")) {
                return DeviceType.WIND;
            } else {
                return DeviceType.GENERIC_SENSOR;
            }
        }

        if (acceptedDataTypes.contains(HSBType.class)) {
            return DeviceType.RGB_LIGHT;
        }

        if (acceptedDataTypes.contains(OpenClosedType.class)) {
            return DeviceType.DOOR;
        }
        if (acceptedDataTypes.contains(OnOffType.class)) {
            return DeviceType.SWITCH;
        }

        return null;
    }

    private Map<TagType, List<String>> getIssTags(Item item) {
        Map<TagType, List<String>> tags = new EnumMap<>(TagType.class);

        for (String tag : item.getTags()) {
            if (tag.startsWith(PREFIX_ISS)) {
                String issTag = tag.substring(PREFIX_ISS.length());
                for (TagType tagType : TagType.values()) {
                    if (issTag.startsWith(tagType.getPrefix() + ':')) {
                        String tagValue = issTag.substring(tagType.getPrefix().length() + 1);
                        if (!tags.containsKey(tagType)) {
                            tags.put(tagType, new LinkedList<String>());
                        } else if (!tagType.isMultiValue()) {
                            logger.error("Found multiple values for tag {} - only first value is used",
                                    tagType.getPrefix());
                        }
                        tags.get(tagType).add(tagValue);
                        break;
                    }
                }
            }
        }

        return tags;
    }

    /**
     * Removes the given item for the device list.
     *
     * @param item Item to remove.
     */
    private void removeItem(Item item) {
        removeItem(item.getName());
    }

    /**
     * Removes the given item for the device list.
     *
     * @param itemName Name of the Item to remove.
     */
    private void removeItem(String itemName) {
        String deviceId = getDeviceId(itemName);

        AbstractDevice device;
        synchronized (deviceRegistry) {
            device = deviceRegistry.remove(deviceId);
        }

        if (device != null) {
            logger.debug("Removing Device from ISS registry for Item: {}", itemName);
            device.destroy();
        }
    }

    /**
     * Generates an unique device ID for the given item.
     * @param item Item to get device ID for.
     * @return Device ID.
     */
    public static String getDeviceId(Item item) {
        return getDeviceId(item.getName());
    }

    /**
     * Generates an unique device ID for the given item name.
     * @param itemName Item name.
     * @return Device ID.
     */
    public static String getDeviceId(String itemName) {
        return DigestUtil.sha1(itemName);
    }

    @Override
    public void added(Item item) {
        logger.debug("Processing item added event");
        parseItem(item);
    }

    @Override
    public void removed(Item item) {
        logger.debug("Processing item removed event");
        removeItem(item);
    }

    @Override
    public void updated(Item oldItem, Item newItem) {
        logger.debug("Processing item updated event");
        removeItem(oldItem);
        parseItem(newItem);
    }

    @Override
    public void allItemsChanged(Collection<String> oldItems) {
        synchronized (deviceRegistry) {
            logger.debug("Processing allItemsChanged event");

            if (oldItems != null) {
                for (String oldItem : oldItems) {
                    removeItem(oldItem);
                }
            }

            if (deviceRegistry.hasDevices()) {
                logger.warn("There are still Devices left after processing all Items from allItemsChanged(): {}",
                        deviceRegistry.getDevices());
                deviceRegistry.clear();
            }

            for (Item item : itemRegistry.getItems()) {
                parseItem(item);
            }
        }
    }

}