org.openhab.binding.yamahareceiver.handler.YamahaBridgeHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.yamahareceiver.handler.YamahaBridgeHandler.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.binding.yamahareceiver.handler;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.config.core.Configuration;
import org.eclipse.smarthome.core.library.types.OnOffType;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
import org.eclipse.smarthome.core.thing.binding.builder.ThingStatusInfoBuilder;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.RefreshType;
import org.openhab.binding.yamahareceiver.YamahaReceiverBindingConstants;
import org.openhab.binding.yamahareceiver.internal.discovery.ZoneDiscoveryService;
import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection;
import org.openhab.binding.yamahareceiver.internal.protocol.ConnectionStateListener;
import org.openhab.binding.yamahareceiver.internal.protocol.DeviceInformation;
import org.openhab.binding.yamahareceiver.internal.protocol.ProtocolFactory;
import org.openhab.binding.yamahareceiver.internal.protocol.ReceivedMessageParseException;
import org.openhab.binding.yamahareceiver.internal.protocol.SystemControl;
import org.openhab.binding.yamahareceiver.internal.state.DeviceInformationState;
import org.openhab.binding.yamahareceiver.internal.state.SystemControlState;
import org.openhab.binding.yamahareceiver.internal.state.SystemControlStateListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link YamahaBridgeHandler} is responsible for fetching basic information about the
 * found AVR and start the zone detection.
 *
 * @author David Graeff - Initial contribution
 */
public class YamahaBridgeHandler extends BaseBridgeHandler
        implements ConnectionStateListener, SystemControlStateListener {
    private Logger logger = LoggerFactory.getLogger(YamahaBridgeHandler.class);
    private int refrehInterval = 60; // Default: Every 1min
    private float relativeVolumeChangeFactor = 0.5f; // Default: 0.5 percent
    private ScheduledFuture<?> refreshTimer;
    private ZoneDiscoveryService zoneDiscoveryService;

    private AbstractConnection connection;
    private SystemControlState systemControlState = new SystemControlState();
    private DeviceInformationState deviceInformationState = new DeviceInformationState();
    private final CountDownLatch loadingDone = new CountDownLatch(1);

    public YamahaBridgeHandler(Bridge bridge) {
        super(bridge);
    }

    /**
     * Return the relative volume change factor
     */
    public float getRelativeVolumeChangeFactor() {
        return relativeVolumeChangeFactor;
    }

    /**
     * Wait until the loading is complete or the timeout occurred.
     *
     * @param timeoutInMs timeout in milliseconds
     * @return Return true if the initial loading is done. This can either be after all requests have been answered by
     *         the AVR or after an error occurred.
     */
    public boolean waitForLoadingDone(long timeoutInMs) throws InterruptedException {
        return loadingDone.await(timeoutInMs, TimeUnit.MILLISECONDS);
    }

    /**
     * @return Return the protocol communication object. This may be null
     *         if the bridge is offline.
     */
    public AbstractConnection getCommunication() {
        return connection;
    }

    @Override
    public void handleCommand(ChannelUID channelUID, Command command) {
        if (connection == null || deviceInformationState.host == null) {
            return;
        }

        if (command instanceof RefreshType) {
            refreshFromState(channelUID);
            return;
        }

        try {
            // Might be extended in the future, therefore a switch statement
            String id = channelUID.getId();
            switch (id) {
            case YamahaReceiverBindingConstants.CHANNEL_POWER:
                SystemControl systemControl = ProtocolFactory.SystemControl(connection, this);
                systemControl.setPower(((OnOffType) command) == OnOffType.ON);
                break;
            default:
                logger.warn(
                        "Channel {} not supported on the yamaha device directly! Try with the zone things instead.",
                        id);
            }
        } catch (IOException | ReceivedMessageParseException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
        }
    }

    private void refreshFromState(ChannelUID channelUID) {
        // Might be extended in the future, therefore a switch statement
        switch (channelUID.getId()) {
        case YamahaReceiverBindingConstants.CHANNEL_POWER:
            updateState(channelUID, systemControlState.power ? OnOffType.ON : OnOffType.OFF);
            break;
        default:
            logger.error("Channel refresh for {} not implemented!", channelUID.getId());
        }
    }

    /**
     * Sets up a refresh timer (using the scheduler) with the CONFIG_REFRESH interval.
     *
     * @param initialWaitTime The delay before the first refresh. Maybe 0 to immediately
     *            initiate a refresh.
     */
    private void setupRefreshTimer(int initialWaitTime) {
        BigDecimal intervalConfig = (BigDecimal) thing.getConfiguration()
                .get(YamahaReceiverBindingConstants.CONFIG_REFRESH);
        if (intervalConfig != null && intervalConfig.intValue() != refrehInterval) {
            refrehInterval = intervalConfig.intValue();
        }

        if (refreshTimer != null) {
            refreshTimer.cancel(false);
        }
        refreshTimer = scheduler.scheduleWithFixedDelay(() -> updateAllZoneInformation(), initialWaitTime,
                refrehInterval, TimeUnit.SECONDS);
    }

    /**
     * Periodically and initially called. This must run in another thread, because
     * all update calls are blocking.
     */
    void updateAllZoneInformation() {
        logger.trace("updateAllZoneInformation");
        try {
            DeviceInformation deviceInformation = ProtocolFactory.DeviceInformation(connection,
                    deviceInformationState);
            deviceInformation.update();
            zoneDiscoveryService.publishZones(deviceInformationState, thing.getUID());

            SystemControl systemControl = ProtocolFactory.SystemControl(connection, this);
            // Set power = true before calling systemControl.update(),
            // otherwise the systemControlStateChanged method would call updateAllZoneInformation() again
            systemControlState.power = true;
            systemControl.update();

            updateProperty(YamahaReceiverBindingConstants.PROPERTY_VERSION, deviceInformationState.version);
            updateProperty(YamahaReceiverBindingConstants.PROPERTY_ASSIGNED_NAME, deviceInformationState.name);

            updateStatus(ThingStatus.ONLINE);

            Bridge bridge = (Bridge) thing;
            for (Thing thing : bridge.getThings()) {
                YamahaZoneThingHandler handler = (YamahaZoneThingHandler) thing.getHandler();
                handler.setDeviceInformationState(deviceInformationState);
                // If thing still thinks that the bridge is offline, update its status.
                if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
                    handler.bridgeStatusChanged(ThingStatusInfoBuilder.create(bridge.getStatus()).build());
                } else if (handler.isCorrectlyInitialized()) {
                    handler.updateZoneInformation();
                }
            }
        } catch (IOException e) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
            systemControlState.invalidate();
            deviceInformationState.invalidate();
            return;
        } catch (ReceivedMessageParseException e) {
            updateProperty(YamahaReceiverBindingConstants.PROPERTY_MENU_ERROR, e.getMessage());
            // Some AVRs send unexpected responses. We log parser exceptions therefore.
            logger.debug("Parse error!", e);
        } finally {
            loadingDone.countDown();
        }
    }

    /**
     * We handle the update ourself to avoid a costly dispose/initialize
     */
    @Override
    public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
        if (!isInitialized()) {
            super.handleConfigurationUpdate(configurationParameters);
            return;
        }

        validateConfigurationParameters(configurationParameters);

        // can be overridden by subclasses
        Configuration configuration = editConfiguration();
        for (Entry<String, Object> configurationParmeter : configurationParameters.entrySet()) {
            configuration.put(configurationParmeter.getKey(), configurationParmeter.getValue());
        }

        updateConfiguration(configuration);

        // Check if host configuration has changed
        String hostConfig = (String) thing.getConfiguration().get(YamahaReceiverBindingConstants.CONFIG_HOST_NAME);
        if (hostConfig != null) {
            connection.setHost(hostConfig);
        }

        // Check if refresh configuration has changed
        BigDecimal intervalConfig = (BigDecimal) thing.getConfiguration()
                .get(YamahaReceiverBindingConstants.CONFIG_REFRESH);
        if (intervalConfig != null && intervalConfig.intValue() != refrehInterval) {
            setupRefreshTimer(intervalConfig.intValue());
        }

        // Read the configuration for the relative volume change factor.
        BigDecimal relativeVolumeChangeFactorBD = (BigDecimal) thing.getConfiguration()
                .get(YamahaReceiverBindingConstants.CONFIG_RELVOLUMECHANGE);
        if (relativeVolumeChangeFactorBD != null) {
            relativeVolumeChangeFactor = relativeVolumeChangeFactorBD.floatValue();
        } else {
            relativeVolumeChangeFactor = 0.5f;
        }
    }

    /**
     * We handle updates of this thing ourself.
     */
    @Override
    public void thingUpdated(Thing thing) {
        this.thing = thing;
    }

    /**
     * Calls createCommunicationObject if the host name is configured correctly.
     */
    @Override
    public void initialize() {
        // Read the configuration for the relative volume change factor.
        BigDecimal relativeVolumeChangeFactorBD = (BigDecimal) thing.getConfiguration()
                .get(YamahaReceiverBindingConstants.CONFIG_RELVOLUMECHANGE);
        if (relativeVolumeChangeFactorBD != null) {
            relativeVolumeChangeFactor = relativeVolumeChangeFactorBD.floatValue();
        }

        String host = (String) thing.getConfiguration().get(YamahaReceiverBindingConstants.CONFIG_HOST_NAME);
        BigDecimal port = (BigDecimal) thing.getConfiguration()
                .get(YamahaReceiverBindingConstants.CONFIG_HOST_PORT);

        if (StringUtils.isEmpty(host) || port == null) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Hostname or port not set!");
            return;
        }

        zoneDiscoveryService = new ZoneDiscoveryService(bundleContext);

        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Waiting for data");
        ProtocolFactory.createConnection(host + ":" + String.valueOf(port.intValue()), this);
    }

    @Override
    public void connectionFailed(String host, Throwable throwable) {
        if (throwable != null) {
            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, throwable.getMessage());
        } else {
            updateStatus(ThingStatus.OFFLINE);
        }
        this.connection = null;
    }

    @Override
    public void connectionEstablished(AbstractConnection connection) {
        this.connection = connection;
        setupRefreshTimer(0);
    }

    @Override
    public void systemControlStateChanged(SystemControlState msg) {
        // If the device was off and now turns on, we trigger a refresh of all zone things.
        // The user might have renamed some of the inputs etc.
        boolean needsCompleteRefresh = msg.power && !systemControlState.power;
        systemControlState = msg;
        updateState(YamahaReceiverBindingConstants.CHANNEL_POWER,
                systemControlState.power ? OnOffType.ON : OnOffType.OFF);
        if (needsCompleteRefresh) {
            updateAllZoneInformation();
        }
    }
}