org.openhab.binding.diyonxbee.internal.DiyOnXBeeBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.diyonxbee.internal.DiyOnXBeeBinding.java

Source

/**
 * Copyright (c) 2010-2019 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.binding.diyonxbee.internal;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.lang.StringUtils;
import org.openhab.binding.diyonxbee.DiyOnXBeeBindingProvider;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TypeParser;
import org.osgi.framework.BundleContext;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rapplogic.xbee.api.ApiId;
import com.rapplogic.xbee.api.PacketListener;
import com.rapplogic.xbee.api.XBee;
import com.rapplogic.xbee.api.XBeeAddress64;
import com.rapplogic.xbee.api.XBeeException;
import com.rapplogic.xbee.api.XBeeResponse;
import com.rapplogic.xbee.api.XBeeTimeoutException;
import com.rapplogic.xbee.api.zigbee.ZNetRxResponse;
import com.rapplogic.xbee.api.zigbee.ZNetTxRequest;
import com.rapplogic.xbee.util.ByteUtils;

/**
 * 
 * @author juergen.richtsfeld@gmail.com
 * @since 1.9
 */
public class DiyOnXBeeBinding extends AbstractBinding<DiyOnXBeeBindingProvider>
        implements PacketListener, ManagedService {

    static final String ITEM_SEPARATOR = "\r\n";

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

    /**
     * The BundleContext. This is only valid when the bundle is ACTIVE. It is
     * set in the activate() method and must not be accessed anymore once the
     * deactivate() method was called or before activate() was called.
     */
    // private BundleContext bundleContext;

    /**
     * the serialPort used to communicate with the XBee
     */
    private String serialPort = "";

    /**
     * The baud rate to use when communicating with the XBee module. Defaults to
     * 9600
     */
    private int baudRate = 9600;

    private XBee xbee;

    /**
     * don't use directly, use {@link #xbeeUsageLock} or {@link #xbeeSetupLock}
     */
    private final ReadWriteLock xbeeLock = new ReentrantReadWriteLock();

    /**
     * use this lock for 'normal' usage of the xbee object
     */
    private final Lock xbeeUsageLock = xbeeLock.readLock();

    /**
     * use this lock when changing the {@link #xbee member}
     */
    private final Lock xbeeSetupLock = xbeeLock.writeLock();

    private ItemRegistry itemRegistry;
    private EventPublisher eventPublisher;

    public DiyOnXBeeBinding() {
    }

    public void activate() {
        logger.debug("Activate");
    }

    public void setEventPublisher(EventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void unsetEventPublisher(EventPublisher evt) {
        this.eventPublisher = null;
    }

    public void setItemRegistry(ItemRegistry itemRegistry) {
        this.itemRegistry = itemRegistry;
    }

    public void removeItemRegistry(ItemRegistry itemRegistry) {
        this.itemRegistry = null;
    }

    /**
     * Called by the SCR to activate the component with its configuration read
     * from CAS
     * 
     * @param bundleContext
     *            BundleContext of the Bundle that defines this component
     * @param configuration
     *            Configuration properties for this component obtained from the
     *            ConfigAdmin service
     */
    public void activate(final BundleContext bundleContext, final Map<String, Object> configuration) {
        // this.bundleContext = bundleContext;
    }

    /**
     * Called by the SCR when the configuration of a binding has been changed
     * through the ConfigAdmin service.
     * 
     * @param configuration
     *            Updated configuration properties
     */
    public void modified(final Map<String, Object> configuration) {
        // update the internal configuration accordingly
    }

    /**
     * Called by the SCR to deactivate the component when either the
     * configuration is removed or mandatory references are no longer satisfied
     * or the component has simply been stopped.
     * 
     * @param reason
     *            Reason code for the deactivation:<br>
     *            <ul>
     *            <li>0  Unspecified
     *            <li>1  The component was disabled
     *            <li>2  A reference became unsatisfied
     *            <li>3  A configuration was changed
     *            <li>4  A configuration was deleted
     *            <li>5  The component was disposed
     *            <li>6  The bundle was stopped
     *            </ul>
     */
    public void deactivate(final int reason) {
        xbeeSetupLock.lock();
        try {
            if (xbee != null) {
                xbee.removePacketListener(this);
                xbee.close();
                xbee = null;
            }
        } finally {
            xbeeSetupLock.unlock();
        }
    }

    /**
     * @{inheritDoc
     */
    @Override
    protected void internalReceiveCommand(String itemName, Command command) {
        // the code being executed when a command was sent on the openHAB
        // event bus goes here. This method is only called if one of the
        // BindingProviders provide a binding for the given 'itemName'.
        logger.debug("internalReceiveCommand({},{}) is called!", itemName, command);

        for (DiyOnXBeeBindingProvider provider : providers) {
            if (provider.providesBindingFor(itemName)) {
                internalReceiveCommand(provider, itemName, command);
            }
        }
    }

    /**
     * 
     * @return if the command was sent successfully
     */
    private boolean internalReceiveCommand(DiyOnXBeeBindingProvider provider, String itemName, Command command) {
        final String remote = provider.getRemote(itemName);
        final int[] remoteAddress = FormatUtil.fromReadableAddress(remote);

        Item item;
        try {
            item = itemRegistry.getItem(itemName);
        } catch (ItemNotFoundException e1) {
            logger.error("unable to get item {}", itemName, e1);
            return false;
        }

        final String commandValue = createCommand(item, command);
        if (commandValue == null) {
            logger.warn("unable to create command {} for item {}", commandValue, itemName);
            return false;
        } else {
            logger.debug("created command {} for item {}", commandValue, itemName);
        }

        final String commandString = new StringBuilder().append(provider.getId(itemName)).append('=')
                .append(commandValue).append('\n').toString();
        final ZNetTxRequest request = new ZNetTxRequest(new XBeeAddress64(remoteAddress),
                createPayload(commandString));

        xbeeUsageLock.lock();
        try {
            if (xbee == null) {
                logger.error("cannot send command to {}  as the XBee module isn't initialized", itemName);
                return false;
            } else {
                final XBeeResponse response = xbee.sendSynchronous(request); // TODO:
                // evaluate
                // response?
                return true;
            }
        } catch (XBeeTimeoutException e) {
            logger.error("failed sending {} to {}", command, itemName, e);
        } catch (XBeeException e) {
            logger.error("failed sending {} to {}", command, itemName, e);
        } finally {
            xbeeUsageLock.unlock();
        }
        return false;
    }

    private int[] createPayload(String requestString) {
        return ByteUtils.stringToIntArray(requestString);
    }

    private String createCommand(final Item item, Command command) {
        if (command == OnOffType.ON) {
            return "ON";
        } else if (command == OnOffType.OFF)
            return "OFF";
        else if (command instanceof IncreaseDecreaseType) {
            final State state = item.getState();
            if (state instanceof HSBType) {
                final HSBType hsbType = (HSBType) state;
                return changeColorBrightness(hsbType, (IncreaseDecreaseType) command);
            } else if (state instanceof PercentType) {
                final PercentType percent = (PercentType) state;
                final PercentType newBrightness = changeBrightness((IncreaseDecreaseType) command, percent);
                return makeHUE(newBrightness.floatValue());
            }
        } else if (command instanceof HSBType) {
            final HSBType hsb = (HSBType) command;
            return makeRGB(hsb.toColor());
        } else if (command instanceof PercentType) {
            final PercentType percent = (PercentType) command;
            return makeHUE(percent.floatValue());
        }

        return null;
    }

    private String changeColorBrightness(final HSBType hsbType, IncreaseDecreaseType increaseDecrease) {
        final PercentType brightness = hsbType.getBrightness();
        final PercentType newBrightness = changeBrightness(increaseDecrease, brightness);
        final HSBType newHSB = new HSBType(hsbType.getHue(), hsbType.getSaturation(), newBrightness);
        return makeRGB(newHSB.toColor());
    }

    private PercentType changeBrightness(IncreaseDecreaseType increaseDecrease, final PercentType brightness) {
        final PercentType newBrightness;
        if (increaseDecrease == IncreaseDecreaseType.DECREASE) {
            BigDecimal changed = brightness.toBigDecimal().subtract(BigDecimal.ONE);
            if (changed.compareTo(BigDecimal.ZERO) < 0)
                changed = BigDecimal.ZERO;
            newBrightness = new PercentType(changed);
        } else {
            BigDecimal changed = brightness.toBigDecimal().add(BigDecimal.ONE);
            if (changed.compareTo(PercentType.HUNDRED.toBigDecimal()) > 0) {
                changed = PercentType.HUNDRED.toBigDecimal();
            }
            newBrightness = new PercentType(changed);
        }
        return newBrightness;
    }

    private String makeRGB(Color color) {
        final StringBuilder sb = new StringBuilder(12);
        sb.append("RGB");
        appendColor(sb, color.getRed());
        appendColor(sb, color.getGreen());
        appendColor(sb, color.getBlue());
        return sb.toString();
    }

    private String makeHUE(final float value) {
        final StringBuilder sb = new StringBuilder(6);
        sb.append("HUE");

        final float brightnessValue = value / 100f * 255f;

        appendColor(sb, (int) brightnessValue);
        return sb.toString();
    }

    private State parseRGBState(final String value) {
        if (!value.startsWith("RGB") || value.length() != 12)
            return null;

        try {
            final int red = Integer.valueOf(value.substring(3, 6));
            final int green = Integer.valueOf(value.substring(6, 9));
            final int blue = Integer.valueOf(value.substring(9, 12));

            return new HSBType(new Color(red, green, blue));
        } catch (NumberFormatException e) {
            logger.warn("cannot parse color from {}", value, e);
        }

        return null;
    }

    private State parseHUE(final String value) {
        if (!value.startsWith("HUE") || value.length() != 6)
            return null;

        try {
            final double hue = Integer.valueOf(value.substring(3, 6));
            return new PercentType(new BigDecimal(hue / 255d * 100d));
        } catch (NumberFormatException e) {
            logger.warn("cannot parse color from {}", value, e);
        }

        return null;
    }

    private void appendColor(StringBuilder sb, int channel) {
        if (channel < 100) {
            sb.append('0');
        }
        if (channel < 10) {
            sb.append('0');
        }
        sb.append(channel);
    }

    /**
     * @{inheritDoc
     */
    @Override
    protected void internalReceiveUpdate(String itemName, State newState) {
        // the code being executed when a state was sent on the openHAB
        // event bus goes here. This method is only called if one of the
        // BindingProviders provide a binding for the given 'itemName'.
        logger.debug("internalReceiveUpdate({},{}) is called!", itemName, newState);
    }

    private final Map<String, String> lastKeys = new HashMap<String, String>();

    @Override
    public void processResponse(XBeeResponse response) {
        if (response.getApiId() == ApiId.ZNET_RX_RESPONSE) {
            final ZNetRxResponse rxResponse = (ZNetRxResponse) response;
            final String message = ByteUtils.toString(rxResponse.getData());
            final String remoteAddress = FormatUtil.readableAddress(rxResponse.getRemoteAddress64().getAddress());

            processResponse(message, remoteAddress);
        }
    }

    void processResponse(final String message, final String remoteAddress) {
        logger.debug("received message: '{}' from '{}'", message, remoteAddress);

        int startIdx = 0;
        do {
            final int idxEquals = message.indexOf('=', startIdx);
            final int idxEnd = message.indexOf(ITEM_SEPARATOR, startIdx);

            if (idxEquals > 0 && idxEnd > 0) {
                if (idxEnd > idxEquals) {
                    final String key = message.substring(startIdx, idxEquals);
                    final String value = message.substring(idxEquals + 1, idxEnd);
                    startIdx = idxEnd + ITEM_SEPARATOR.length();
                    tryUpdate(key, value, remoteAddress);
                    lastKeys.remove(remoteAddress);
                } else {
                    final String lastKey = lastKeys.remove(remoteAddress);
                    if (lastKey != null) {
                        final String value = message.substring(startIdx, idxEnd);
                        tryUpdate(lastKey, value, remoteAddress);
                    }
                    startIdx = idxEnd + ITEM_SEPARATOR.length();
                }
            } else if (idxEquals > 0) {
                lastKeys.put(remoteAddress, message.substring(startIdx, idxEquals));
                startIdx = -1;
            } else {
                startIdx = -1;
            }
        } while (startIdx > 0);
    }

    private void tryUpdate(final String key, final String value, final String remoteAddress) {
        logger.debug("trying to set {} of {} to {}", key, remoteAddress, value);
        boolean updated = false;
        for (final DiyOnXBeeBindingProvider provider : providers) {

            for (final String itemName : provider.getItemNames()) {
                final String id = provider.getId(itemName);
                final String remote = provider.getRemote(itemName);

                if (key.equals(id) && remote.equals(remoteAddress)) {
                    final List<Class<? extends State>> availableTypes = provider.getAvailableItemTypes(itemName);
                    final State state = parseState(value, availableTypes, provider, itemName);
                    if (state != null) {
                        updated = true;
                        eventPublisher.postUpdate(itemName, state);
                    }
                }
            }
        }
        if (!updated) {
            logger.warn("unmatched item: key='{}', value='{}' from '{}'", key, value, remoteAddress);
        }
    }

    private State parseState(final String value, final List<Class<? extends State>> availableTypes,
            DiyOnXBeeBindingProvider provider, String itemName) {
        State state = TypeParser.parseState(availableTypes, value);
        if (state == null) {
            state = parseCustomState(value, availableTypes);
        } else if (state.getClass() == DecimalType.class) {
            final Integer max = provider.getMaxValue(itemName);

            if (max != null) {
                final DecimalType type = (DecimalType) state;
                final double percentage = type.doubleValue() / max.intValue() * 100d;
                return new DecimalType(percentage);
            }
        }
        return state;
    }

    private State parseCustomState(String value, List<Class<? extends State>> availableTypes) {
        for (Class<? extends State> clazz : availableTypes) {
            if (clazz == HSBType.class) {
                final State rgb = parseRGBState(value);
                if (rgb != null) {
                    return rgb;
                }
            }
            if (clazz == PercentType.class) {
                final State hue = parseHUE(value);
                if (hue != null) {
                    return hue;
                }
            }
        }
        return null;
    }

    @Override
    public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
        {
            final String serialPort = (String) properties.get("serialPort");
            if (StringUtils.isNotBlank(serialPort)) {
                this.serialPort = serialPort;
            }
        }

        {
            final String baudRate = (String) properties.get("baudRate");
            if (StringUtils.isNotBlank(baudRate)) {
                this.baudRate = Integer.parseInt(baudRate);
            }
        }

        String canonical = serialPort;
        try {
            // This code below enables to use a device name in
            // /dev/serial/by-id/... on linux
            File device = new File(serialPort);
            if (device.canRead()) {
                canonical = device.getCanonicalPath();
            }
        } catch (IOException e1) {
            logger.info("unable to get canonical path for '{}'", serialPort);
            canonical = serialPort;
        }

        xbeeSetupLock.lock();
        try {
            if (xbee != null) {
                xbee.removePacketListener(this);
                xbee.close();
                xbee = null;
            }
            xbee = new XBee();

            try {
                logger.info("opening XBee communication on '{}'", canonical);
                xbee.open(canonical, baudRate);
            } catch (XBeeException e) {
                logger.error("failed to open connection to XBee module", e);
                xbee = null;
                return;
            }
        } finally {
            xbeeSetupLock.unlock();
        }

        xbee.addPacketListener(this);
    }
}