/** * Copyright (c) 2014-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.eclipse.smarthome.binding.fsinternetradio.handler; import static java.util.concurrent.TimeUnit.SECONDS; import static org.eclipse.smarthome.binding.fsinternetradio.FSInternetRadioBindingConstants.*; import java.math.BigDecimal; import java.util.concurrent.ScheduledFuture; import org.eclipse.smarthome.binding.fsinternetradio.internal.radio.FrontierSiliconRadio; import org.eclipse.smarthome.core.library.types.DecimalType; import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.PercentType; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.library.types.UpDownType; import org.eclipse.smarthome.core.thing.Channel; 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.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.commons.lang.StringUtils; /** * The {@link FSInternetRadioHandler} is responsible for handling commands, which are * sent to one of the channels. * * @author Patrick Koenemann - Initial contribution * @author Mihaela Memova - removed the unused boolean parameter, changed the check for the PIN */ public class FSInternetRadioHandler extends BaseThingHandler { private Logger logger = LoggerFactory.getLogger(FSInternetRadioHandler.class); private FrontierSiliconRadio radio; /** Job that runs {@link #updateRunnable}. */ private ScheduledFuture<?> updateJob; /** Runnable for job {@link #updateJob} for periodic refresh. */ private Runnable updateRunnable = new Runnable() { @Override public void run() { if (radio == null) { // radio is not set, so set all channels to 'undefined' for (Channel channel : getThing().getChannels()) { updateState(channel.getUID(), UnDefType.UNDEF); } // now let's silently check if it's back online radioLogin(); return; // if login is successful, this method is called again :-) } try { final boolean radioOn = radio.getPower(); for (Channel channel : getThing().getChannels()) { if (!radioOn && !CHANNEL_POWER.equals(channel.getUID().getId())) { // if radio is off, set all channels (except for 'POWER') to 'undefined' updateState(channel.getUID(), UnDefType.UNDEF); } else if (isLinked(channel.getUID().getId())) { // update all channels that are linked switch (channel.getUID().getId()) { case CHANNEL_POWER: updateState(channel.getUID(), radioOn ? OnOffType.ON : OnOffType.OFF); break; case CHANNEL_VOLUME_ABSOLUTE: updateState(channel.getUID(), DecimalType.valueOf(String.valueOf(radio.getVolumeAbsolute()))); break; case CHANNEL_VOLUME_PERCENT: updateState(channel.getUID(), PercentType.valueOf(String.valueOf(radio.getVolumePercent()))); break; case CHANNEL_MODE: updateState(channel.getUID(), DecimalType.valueOf(String.valueOf(radio.getMode()))); break; case CHANNEL_MUTE: updateState(channel.getUID(), radio.getMuted() ? OnOffType.ON : OnOffType.OFF); break; case CHANNEL_PRESET: // preset is write-only, ignore break; case CHANNEL_PLAY_INFO_NAME: updateState(channel.getUID(), StringType.valueOf(radio.getPlayInfoName())); break; case CHANNEL_PLAY_INFO_TEXT: updateState(channel.getUID(), StringType.valueOf(radio.getPlayInfoText())); break; default: logger.warn("Ignoring unknown channel during update: " + channel.getLabel()); } } } updateStatus(ThingStatus.ONLINE); // set it back online, maybe it was offline before } catch (Exception e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } }; public FSInternetRadioHandler(Thing thing) { super(thing); } @Override public void initialize() { // Long running initialization should be done asynchronously in background radioLogin(); // also schedule a thread for polling with configured refresh rate final BigDecimal period = (BigDecimal) getThing().getConfiguration().get(CONFIG_PROPERTY_REFRESH); if (period != null && period.intValue() > 0) { updateJob = scheduler.scheduleWithFixedDelay(updateRunnable, period.intValue(), period.intValue(), SECONDS); } } private void radioLogin() { // read configuration and spawn thread to establish connection to device final String ip = (String) getThing().getConfiguration().get(CONFIG_PROPERTY_IP); final BigDecimal port = (BigDecimal) getThing().getConfiguration().get(CONFIG_PROPERTY_PORT); final String pin = (String) getThing().getConfiguration().get(CONFIG_PROPERTY_PIN); if (ip == null || StringUtils.isEmpty(pin) || port.intValue() == 0) { // configuration incomplete updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration incomplete"); } else { scheduler.execute(new Runnable() { @Override public void run() { logger.debug("creating new connection to " + ip + ":" + port); try { final FrontierSiliconRadio tmpRadio = new FrontierSiliconRadio(ip, port.intValue(), pin); tmpRadio.login(); radio = tmpRadio; // login successful, now store radio in field // Thing initialized. If done set status to ONLINE to indicate proper working. updateStatus(ThingStatus.ONLINE); // now update all channels! updateRunnable.run(); } catch (Exception e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } }); } } @Override public void dispose() { if (updateJob != null) { updateJob.cancel(true); } updateJob = null; radio = null; } @Override public void handleCommand(final ChannelUID channelUID, final Command command) { if (radio == null) { // connection to radio is not initialized, log ignored command and set status, if it is not already offline logger.debug("Ignoring command " + channelUID.getId() + " = " + command + " because device is offline."); if (ThingStatus.ONLINE.equals(getThing().getStatus())) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); } return; } try { switch (channelUID.getId()) { case CHANNEL_POWER: if (OnOffType.ON.equals(command)) { radio.setPower(true); } else if (OnOffType.OFF.equals(command)) { radio.setPower(false); } // now all items should be updated! (wait some seconds so that text items are up-to-date) scheduler.schedule(updateRunnable, 4, SECONDS); break; case CHANNEL_VOLUME_PERCENT: if (IncreaseDecreaseType.INCREASE.equals(command) || UpDownType.UP.equals(command)) { radio.increaseVolumeAbsolute(); } else if (IncreaseDecreaseType.DECREASE.equals(command) || UpDownType.DOWN.equals(command)) { radio.decreaseVolumeAbsolute(); } else if (command instanceof PercentType) { radio.setVolumePercent(((PercentType) command).intValue()); } // absolute value should also be updated now, so let's update all items scheduler.schedule(updateRunnable, 1, SECONDS); break; case CHANNEL_VOLUME_ABSOLUTE: if (IncreaseDecreaseType.INCREASE.equals(command) || UpDownType.UP.equals(command)) { radio.increaseVolumeAbsolute(); } else if (IncreaseDecreaseType.DECREASE.equals(command) || UpDownType.DOWN.equals(command)) { radio.decreaseVolumeAbsolute(); } else if (command instanceof DecimalType) { radio.setVolumeAbsolute(((DecimalType) command).intValue()); } // percent value should also be updated now, so let's update all items scheduler.schedule(updateRunnable, 1, SECONDS); break; case CHANNEL_MODE: if (command instanceof DecimalType) { radio.setMode(((DecimalType) command).intValue()); } break; case CHANNEL_PRESET: if (command instanceof DecimalType) { radio.setPreset(((DecimalType) command).intValue()); } break; case CHANNEL_MUTE: if (command instanceof OnOffType) { radio.setMuted(OnOffType.ON.equals(command)); } break; default: logger.warn("Ignoring unknown command: " + command); } // make sure that device state is online updateStatus(ThingStatus.ONLINE); } catch (Exception e) { // set device state to offline updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } }