org.eclipse.smarthome.binding.homematic.internal.communicator.client.RpcClient.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.smarthome.binding.homematic.internal.communicator.client.RpcClient.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.homematic.internal.communicator.client;

import static org.eclipse.smarthome.binding.homematic.HomematicBindingConstants.*;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.binding.homematic.HomematicBindingConstants;
import org.eclipse.smarthome.binding.homematic.internal.common.HomematicConfig;
import org.eclipse.smarthome.binding.homematic.internal.communicator.message.RpcRequest;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.GetAllScriptsParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.GetAllSystemVariablesParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.GetDeviceDescriptionParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.GetParamsetDescriptionParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.GetParamsetParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.GetValueParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.HomegearLoadDeviceNamesParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.ListBidcosInterfacesParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.ListDevicesParser;
import org.eclipse.smarthome.binding.homematic.internal.communicator.parser.RssiInfoParser;
import org.eclipse.smarthome.binding.homematic.internal.model.HmChannel;
import org.eclipse.smarthome.binding.homematic.internal.model.HmDatapoint;
import org.eclipse.smarthome.binding.homematic.internal.model.HmDevice;
import org.eclipse.smarthome.binding.homematic.internal.model.HmGatewayInfo;
import org.eclipse.smarthome.binding.homematic.internal.model.HmInterface;
import org.eclipse.smarthome.binding.homematic.internal.model.HmParamsetType;
import org.eclipse.smarthome.binding.homematic.internal.model.HmRssiInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Client implementation for sending messages via BIN-RPC to a Homematic gateway.
 *
 * @author Gerhard Riegler - Initial contribution
 */
public abstract class RpcClient<T> {
    private final Logger logger = LoggerFactory.getLogger(RpcClient.class);
    protected static final int MAX_RPC_RETRY = 1;

    protected HomematicConfig config;

    public RpcClient(HomematicConfig config) {
        this.config = config;
    }

    /**
     * Disposes the client.
     */
    public abstract void dispose();

    /**
     * Returns a RpcRequest for this client.
     */
    protected abstract RpcRequest<T> createRpcRequest(String methodName);

    /**
     * Returns the callback url for this client.
     */
    protected abstract String getRpcCallbackUrl();

    /**
     * Sends the RPC message to the gateway.
     */
    protected abstract Object[] sendMessage(int port, RpcRequest<T> request) throws IOException;

    /**
     * Register a callback for the specified interface where the Homematic gateway can send its events.
     */
    public void init(HmInterface hmInterface, String clientId) throws IOException {
        RpcRequest<T> request = createRpcRequest("init");
        request.addArg(getRpcCallbackUrl());
        request.addArg(clientId);
        if (config.getGatewayInfo().isHomegear()) {
            request.addArg(new Integer(0x22));
        }
        sendMessage(config.getRpcPort(hmInterface), request);
    }

    /**
     * Release a callback for the specified interface.
     */
    public void release(HmInterface hmInterface) throws IOException {
        RpcRequest<T> request = createRpcRequest("init");
        request.addArg(getRpcCallbackUrl());
        sendMessage(config.getRpcPort(hmInterface), request);
    }

    /**
     * Sends a ping to the specified interface.
     */
    public void ping(HmInterface hmInterface, String callerId) throws IOException {
        RpcRequest<T> request = createRpcRequest("ping");
        request.addArg(callerId);
        sendMessage(config.getRpcPort(hmInterface), request);
    }

    /**
     * Returns the info of all BidCos interfaces available on the gateway.
     */
    public ListBidcosInterfacesParser listBidcosInterfaces(HmInterface hmInterface) throws IOException {
        RpcRequest<T> request = createRpcRequest("listBidcosInterfaces");
        return new ListBidcosInterfacesParser().parse(sendMessage(config.getRpcPort(hmInterface), request));
    }

    /**
     * Returns some infos of the gateway.
     */
    private GetDeviceDescriptionParser getDeviceDescription(HmInterface hmInterface) throws IOException {
        RpcRequest<T> request = createRpcRequest("getDeviceDescription");
        request.addArg("BidCoS-RF");
        return new GetDeviceDescriptionParser().parse(sendMessage(config.getRpcPort(hmInterface), request));
    }

    /**
     * Returns all variable metadata and values from a Homegear gateway.
     */
    public void getAllSystemVariables(HmChannel channel) throws IOException {
        RpcRequest<T> request = createRpcRequest("getAllSystemVariables");
        new GetAllSystemVariablesParser(channel).parse(sendMessage(config.getRpcPort(channel), request));
    }

    /**
     * Loads all device names from a Homegear gateway.
     */
    public void loadDeviceNames(HmInterface hmInterface, Collection<HmDevice> devices) throws IOException {
        RpcRequest<T> request = createRpcRequest("getDeviceInfo");
        new HomegearLoadDeviceNamesParser(devices).parse(sendMessage(config.getRpcPort(hmInterface), request));
    }

    /**
     * Returns true, if the interface is available on the gateway.
     */
    public void checkInterface(HmInterface hmInterface) throws IOException {
        RpcRequest<T> request = createRpcRequest("init");
        request.addArg("http://openhab.validation:1000");
        sendMessage(config.getRpcPort(hmInterface), request);
    }

    /**
     * Returns all script metadata from a Homegear gateway.
     */
    public void getAllScripts(HmChannel channel) throws IOException {
        RpcRequest<T> request = createRpcRequest("getAllScripts");
        new GetAllScriptsParser(channel).parse(sendMessage(config.getRpcPort(channel), request));
    }

    /**
     * Returns all device and channel metadata.
     */
    public Collection<HmDevice> listDevices(HmInterface hmInterface) throws IOException {
        RpcRequest<T> request = createRpcRequest("listDevices");
        return new ListDevicesParser(hmInterface, config)
                .parse(sendMessage(config.getRpcPort(hmInterface), request));
    }

    /**
     * Loads all datapoint metadata into the given channel.
     */
    public void addChannelDatapoints(HmChannel channel, HmParamsetType paramsetType) throws IOException {
        if (isConfigurationChannel(channel) && paramsetType != HmParamsetType.MASTER) {
            // The configuration channel only has a MASTER Paramset, so there is nothing to load
            return;
        }

        RpcRequest<T> request = createRpcRequest("getParamsetDescription");
        request.addArg(getRpcAddress(channel.getDevice().getAddress()) + getChannelSuffix(channel));
        request.addArg(paramsetType.toString());
        new GetParamsetDescriptionParser(channel, paramsetType)
                .parse(sendMessage(config.getRpcPort(channel), request));
    }

    /**
     * Sets all datapoint values for the given channel.
     */
    public void setChannelDatapointValues(HmChannel channel, HmParamsetType paramsetType) throws IOException {
        if (isConfigurationChannel(channel) && paramsetType != HmParamsetType.MASTER) {
            // The configuration channel only has a MASTER Paramset, so there is nothing to load
            return;
        }

        RpcRequest<T> request = createRpcRequest("getParamset");
        request.addArg(getRpcAddress(channel.getDevice().getAddress()) + getChannelSuffix(channel));
        request.addArg(paramsetType.toString());
        if (channel.getDevice().getHmInterface() == HmInterface.CUXD && paramsetType == HmParamsetType.VALUES) {
            setChannelDatapointValues(channel);
        } else {
            try {
                new GetParamsetParser(channel, paramsetType)
                        .parse(sendMessage(config.getRpcPort(channel), request));
            } catch (UnknownRpcFailureException ex) {
                if (paramsetType == HmParamsetType.VALUES) {
                    logger.debug(
                            "RpcResponse unknown RPC failure (-1 Failure), fetching values with another API method for device: {}, channel: {}, paramset: {}",
                            channel.getDevice().getAddress(), channel.getNumber(), paramsetType);
                    setChannelDatapointValues(channel);
                } else {
                    throw ex;
                }
            }
        }
    }

    /**
     * Reads all VALUES datapoints individually, fallback method if setChannelDatapointValues throws a -1 Failure
     * exception.
     */
    private void setChannelDatapointValues(HmChannel channel) throws IOException {
        for (HmDatapoint dp : channel.getDatapoints()) {
            getDatapointValue(dp);
        }
    }

    /**
     * Tries to identify the gateway and returns the GatewayInfo.
     */
    public HmGatewayInfo getGatewayInfo(String id) throws IOException {
        boolean isHomegear = false;
        GetDeviceDescriptionParser ddParser;
        ListBidcosInterfacesParser biParser;

        try {
            ddParser = getDeviceDescription(HmInterface.RF);
            isHomegear = StringUtils.equalsIgnoreCase(ddParser.getType(), "Homegear");
        } catch (IOException ex) {
            // can't load gateway infos via RF interface
            ddParser = new GetDeviceDescriptionParser();
        }

        try {
            biParser = listBidcosInterfaces(HmInterface.RF);
        } catch (IOException ex) {
            biParser = listBidcosInterfaces(HmInterface.HMIP);
        }

        HmGatewayInfo gatewayInfo = new HmGatewayInfo();
        gatewayInfo.setAddress(biParser.getGatewayAddress());
        if (isHomegear) {
            gatewayInfo.setId(HmGatewayInfo.ID_HOMEGEAR);
            gatewayInfo.setType(ddParser.getType());
            gatewayInfo.setFirmware(ddParser.getFirmware());
        } else if ((StringUtils.startsWithIgnoreCase(biParser.getType(), "CCU")
                || StringUtils.startsWithIgnoreCase(biParser.getType(), "HMIP_CCU")
                || StringUtils.startsWithIgnoreCase(ddParser.getType(), "HM-RCV-50") || config.isCCUType())
                && !config.isNoCCUType()) {
            gatewayInfo.setId(HmGatewayInfo.ID_CCU);
            String type = StringUtils.isBlank(biParser.getType()) ? "CCU" : biParser.getType();
            gatewayInfo.setType(type);
            gatewayInfo
                    .setFirmware(ddParser.getFirmware() != null ? ddParser.getFirmware() : biParser.getFirmware());
        } else {
            gatewayInfo.setId(HmGatewayInfo.ID_DEFAULT);
            gatewayInfo.setType(biParser.getType());
            gatewayInfo.setFirmware(biParser.getFirmware());
        }

        if (gatewayInfo.isCCU() || config.hasRfPort()) {
            gatewayInfo.setRfInterface(hasInterface(HmInterface.RF, id));
        }

        if (gatewayInfo.isCCU() || config.hasWiredPort()) {
            gatewayInfo.setWiredInterface(hasInterface(HmInterface.WIRED, id));
        }

        if (gatewayInfo.isCCU() || config.hasHmIpPort()) {
            gatewayInfo.setHmipInterface(hasInterface(HmInterface.HMIP, id));
        }

        if (gatewayInfo.isCCU() || config.hasCuxdPort()) {
            gatewayInfo.setCuxdInterface(hasInterface(HmInterface.CUXD, id));
        }

        if (gatewayInfo.isCCU() || config.hasGroupPort()) {
            gatewayInfo.setGroupInterface(hasInterface(HmInterface.GROUP, id));
        }

        return gatewayInfo;
    }

    /**
     * Returns true, if a connection is possible with the given interface.
     */
    private boolean hasInterface(HmInterface hmInterface, String id) throws IOException {
        try {
            checkInterface(hmInterface);
            return true;
        } catch (IOException ex) {
            logger.info("Interface '{}' on gateway '{}' not available, disabling support", hmInterface, id);
            return false;
        }
    }

    /**
     * Sets the value of the datapoint using the provided rx transmission mode.
     *
     * @param dp The datapoint to set
     * @param value The new value to set on the datapoint
     * @param rxMode The rx mode to use for the transmission of the datapoint value
     *            ({@link HomematicBindingConstants#RX_BURST_MODE "BURST"} for burst mode,
     *            {@link HomematicBindingConstants#RX_WAKEUP_MODE "WAKEUP"} for wakeup mode, or null for the default
     *            mode)
     */
    public void setDatapointValue(HmDatapoint dp, Object value, String rxMode) throws IOException {
        if (dp.isIntegerType() && value instanceof Double) {
            value = ((Number) value).intValue();
        }

        RpcRequest<T> request;
        if (HmParamsetType.VALUES == dp.getParamsetType()) {
            request = createRpcRequest("setValue");
            request.addArg(
                    getRpcAddress(dp.getChannel().getDevice().getAddress()) + getChannelSuffix(dp.getChannel()));
            request.addArg(dp.getName());
            request.addArg(value);
            configureRxMode(request, rxMode);
        } else {
            request = createRpcRequest("putParamset");
            request.addArg(
                    getRpcAddress(dp.getChannel().getDevice().getAddress()) + getChannelSuffix(dp.getChannel()));
            request.addArg(HmParamsetType.MASTER.toString());
            Map<String, Object> paramSet = new HashMap<String, Object>();
            paramSet.put(dp.getName(), value);
            request.addArg(paramSet);
            configureRxMode(request, rxMode);
        }
        sendMessage(config.getRpcPort(dp.getChannel()), request);
    }

    protected void configureRxMode(RpcRequest<T> request, String rxMode) {
        if (rxMode != null) {
            if (RX_BURST_MODE.equals(rxMode) || RX_WAKEUP_MODE.equals(rxMode)) {
                request.addArg(rxMode);
            }
        }
    }

    /**
     * Retrieves the value of a single {@link HmDatapoint} from the device. Can only be used for the paramset "VALUES".
     *
     * @param dp The HmDatapoint that shall be loaded
     * @throws IOException If there is a problem while communicating to the gateway
     */
    public void getDatapointValue(HmDatapoint dp) throws IOException {
        if (dp.isReadable() && !dp.isVirtual() && dp.getParamsetType() == HmParamsetType.VALUES) {
            RpcRequest<T> request = createRpcRequest("getValue");
            request.addArg(
                    getRpcAddress(dp.getChannel().getDevice().getAddress()) + getChannelSuffix(dp.getChannel()));
            request.addArg(dp.getName());
            new GetValueParser(dp).parse(sendMessage(config.getRpcPort(dp.getChannel()), request));
        }
    }

    /**
     * Sets the value of a system variable on a Homegear gateway.
     */
    public void setSystemVariable(HmDatapoint dp, Object value) throws IOException {
        RpcRequest<T> request = createRpcRequest("setSystemVariable");
        request.addArg(dp.getInfo());
        request.addArg(value);
        sendMessage(config.getRpcPort(dp.getChannel()), request);
    }

    /**
     * Executes a script on the Homegear gateway.
     */
    public void executeScript(HmDatapoint dp) throws IOException {
        RpcRequest<T> request = createRpcRequest("runScript");
        request.addArg(dp.getInfo());
        sendMessage(config.getRpcPort(dp.getChannel()), request);
    }

    /**
     * Enables/disables the install mode for given seconds.
     *
     * @param hmInterface specifies the interface to enable / disable install mode on
     * @param enable if <i>true</i> it will be enabled, otherwise disabled
     * @param seconds desired duration of install mode
     * @throws IOException if RpcClient fails to propagate command
     */
    public void setInstallMode(HmInterface hmInterface, boolean enable, int seconds) throws IOException {
        RpcRequest<T> request = createRpcRequest("setInstallMode");
        request.addArg(enable);
        request.addArg(seconds);
        request.addArg(INSTALL_MODE_NORMAL);
        logger.debug("Submitting setInstallMode(on={}, time={}, mode={}) ", enable, seconds, INSTALL_MODE_NORMAL);
        sendMessage(config.getRpcPort(hmInterface), request);
    }

    /**
     * Returns the remaining time of <i>install_mode==true</i>
     *
     * @param hmInterface specifies the interface on which install mode status is requested
     * @return current duration in seconds that the controller will remain in install mode,
     *         value of 0 means that the install mode is disabled
     * @throws IOException if RpcClient fails to propagate command
     */
    public int getInstallMode(HmInterface hmInterface) throws IOException {
        RpcRequest<T> request = createRpcRequest("getInstallMode");
        Object[] result = sendMessage(config.getRpcPort(hmInterface), request);
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "Checking InstallMode: getInstallMode() request returned {} (remaining seconds in InstallMode=true)",
                    result);
        }
        try {
            return (int) result[0];
        } catch (Exception cause) {
            IOException wrappedException = new IOException(
                    "Failed to request install mode from interface " + hmInterface);
            wrappedException.initCause(cause);
            throw wrappedException;
        }
    }

    /**
     * Deletes the device from the gateway.
     */
    public void deleteDevice(HmDevice device, int flags) throws IOException {
        RpcRequest<T> request = createRpcRequest("deleteDevice");
        request.addArg(device.getAddress());
        request.addArg(flags);
        sendMessage(config.getRpcPort(device.getHmInterface()), request);
    }

    /**
     * Returns the rpc address from a device address, correctly handling groups.
     */
    private String getRpcAddress(String address) {
        if (address != null && address.startsWith("T-")) {
            address = "*" + address.substring(2);
        }
        return address;
    }

    /**
     * Returns the rssi values for all devices.
     */
    public List<HmRssiInfo> loadRssiInfo(HmInterface hmInterface) throws IOException {
        RpcRequest<T> request = createRpcRequest("rssiInfo");
        return new RssiInfoParser(config).parse(sendMessage(config.getRpcPort(hmInterface), request));
    }

    /**
     * Returns the address suffix that specifies the channel for a given HmChannel. This is either a colon ":" followed
     * by the channel number, or the empty string for a configuration channel.
     */
    private String getChannelSuffix(HmChannel channel) {
        return isConfigurationChannel(channel) ? "" : ":" + channel.getNumber();
    }

    /**
     * Checks whether a channel is a configuration channel. The configuration channel of a device encapsulates the
     * MASTER Paramset that does not belong to one of its actual channels.
     */
    private boolean isConfigurationChannel(HmChannel channel) {
        return channel.getNumber() == CONFIGURATION_CHANNEL_NUMBER;
    }

}