2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.fsinternetradio.internal.handler;
15 import static java.util.concurrent.TimeUnit.SECONDS;
16 import static org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants.*;
18 import java.math.BigDecimal;
19 import java.util.concurrent.ScheduledFuture;
21 import org.eclipse.jetty.client.HttpClient;
22 import org.openhab.binding.fsinternetradio.internal.radio.FrontierSiliconRadio;
23 import org.openhab.core.library.types.DecimalType;
24 import org.openhab.core.library.types.IncreaseDecreaseType;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.library.types.PercentType;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.library.types.UpDownType;
29 import org.openhab.core.thing.Channel;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.binding.BaseThingHandler;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.UnDefType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The {@link FSInternetRadioHandler} is responsible for handling commands, which are
42 * sent to one of the channels.
44 * @author Patrick Koenemann - Initial contribution
45 * @author Mihaela Memova - removed the unused boolean parameter, changed the check for the PIN
46 * @author Svilen Valkanov - changed handler initialization
48 public class FSInternetRadioHandler extends BaseThingHandler {
50 private final Logger logger = LoggerFactory.getLogger(FSInternetRadioHandler.class);
52 FrontierSiliconRadio radio;
53 private final HttpClient httpClient;
55 /** Job that runs {@link #updateRunnable}. */
56 private ScheduledFuture<?> updateJob;
58 /** Runnable for job {@link #updateJob} for periodic refresh. */
59 private final Runnable updateRunnable = new Runnable() {
62 if (!radio.isLoggedIn()) {
63 // radio is not set, so set all channels to 'undefined'
64 for (Channel channel : getThing().getChannels()) {
65 updateState(channel.getUID(), UnDefType.UNDEF);
67 // now let's silently check if it's back online
69 return; // if login is successful, this method is called again :-)
72 final boolean radioOn = radio.getPower();
73 for (Channel channel : getThing().getChannels()) {
74 if (!radioOn && !CHANNEL_POWER.equals(channel.getUID().getId())) {
75 // if radio is off, set all channels (except for 'POWER') to 'undefined'
76 updateState(channel.getUID(), UnDefType.UNDEF);
77 } else if (isLinked(channel.getUID().getId())) {
78 // update all channels that are linked
79 switch (channel.getUID().getId()) {
81 updateState(channel.getUID(), radioOn ? OnOffType.ON : OnOffType.OFF);
83 case CHANNEL_VOLUME_ABSOLUTE:
84 updateState(channel.getUID(),
85 DecimalType.valueOf(String.valueOf(radio.getVolumeAbsolute())));
87 case CHANNEL_VOLUME_PERCENT:
88 updateState(channel.getUID(),
89 PercentType.valueOf(String.valueOf(radio.getVolumePercent())));
92 updateState(channel.getUID(), DecimalType.valueOf(String.valueOf(radio.getMode())));
95 updateState(channel.getUID(), radio.getMuted() ? OnOffType.ON : OnOffType.OFF);
98 // preset is write-only, ignore
100 case CHANNEL_PLAY_INFO_NAME:
101 updateState(channel.getUID(), StringType.valueOf(radio.getPlayInfoName()));
103 case CHANNEL_PLAY_INFO_TEXT:
104 updateState(channel.getUID(), StringType.valueOf(radio.getPlayInfoText()));
107 logger.warn("Ignoring unknown channel during update: {}", channel.getLabel());
111 updateStatus(ThingStatus.ONLINE); // set it back online, maybe it was offline before
112 } catch (Exception e) {
113 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
118 public FSInternetRadioHandler(Thing thing, HttpClient httpClient) {
120 this.httpClient = httpClient;
124 public void initialize() {
125 // read configuration
126 final String ip = (String) getThing().getConfiguration().get(CONFIG_PROPERTY_IP);
127 final BigDecimal port = (BigDecimal) getThing().getConfiguration().get(CONFIG_PROPERTY_PORT);
128 final String pin = (String) getThing().getConfiguration().get(CONFIG_PROPERTY_PIN);
130 if (ip == null || pin == null || pin.isEmpty() || port.intValue() == 0) {
131 // configuration incomplete
132 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration incomplete");
134 radio = new FrontierSiliconRadio(ip, port.intValue(), pin, httpClient);
135 logger.debug("Initializing connection to {}:{}", ip, port);
137 // Long running initialization should be done asynchronously in background
140 // also schedule a thread for polling with configured refresh rate
141 final BigDecimal period = (BigDecimal) getThing().getConfiguration().get(CONFIG_PROPERTY_REFRESH);
142 if (period != null && period.intValue() > 0) {
143 updateJob = scheduler.scheduleWithFixedDelay(updateRunnable, period.intValue(), period.intValue(),
149 private void radioLogin() {
150 scheduler.execute(new Runnable() {
155 // Thing initialized. If done set status to ONLINE to indicate proper working.
156 updateStatus(ThingStatus.ONLINE);
158 // now update all channels!
159 updateRunnable.run();
161 } catch (Exception e) {
162 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
169 public void dispose() {
170 if (updateJob != null) {
171 updateJob.cancel(true);
178 public void handleCommand(final ChannelUID channelUID, final Command command) {
179 if (!radio.isLoggedIn()) {
180 // connection to radio is not initialized, log ignored command and set status, if it is not already offline
181 logger.debug("Ignoring command {} = {} because device is offline.", channelUID.getId(), command);
182 if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
183 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
188 switch (channelUID.getId()) {
190 if (OnOffType.ON.equals(command)) {
191 radio.setPower(true);
192 } else if (OnOffType.OFF.equals(command)) {
193 radio.setPower(false);
195 // now all items should be updated! (wait some seconds so that text items are up-to-date)
196 scheduler.schedule(updateRunnable, 4, SECONDS);
198 case CHANNEL_VOLUME_PERCENT:
199 if (IncreaseDecreaseType.INCREASE.equals(command) || UpDownType.UP.equals(command)) {
200 radio.increaseVolumeAbsolute();
201 } else if (IncreaseDecreaseType.DECREASE.equals(command) || UpDownType.DOWN.equals(command)) {
202 radio.decreaseVolumeAbsolute();
203 } else if (command instanceof PercentType percentCommand) {
204 radio.setVolumePercent(percentCommand.intValue());
206 // absolute value should also be updated now, so let's update all items
207 scheduler.schedule(updateRunnable, 1, SECONDS);
209 case CHANNEL_VOLUME_ABSOLUTE:
210 if (IncreaseDecreaseType.INCREASE.equals(command) || UpDownType.UP.equals(command)) {
211 radio.increaseVolumeAbsolute();
212 } else if (IncreaseDecreaseType.DECREASE.equals(command) || UpDownType.DOWN.equals(command)) {
213 radio.decreaseVolumeAbsolute();
214 } else if (command instanceof DecimalType decimalCommand) {
215 radio.setVolumeAbsolute(decimalCommand.intValue());
217 // percent value should also be updated now, so let's update all items
218 scheduler.schedule(updateRunnable, 1, SECONDS);
221 if (command instanceof DecimalType decimalCommand) {
222 radio.setMode(decimalCommand.intValue());
226 if (command instanceof DecimalType decimalCommand) {
227 radio.setPreset(decimalCommand.intValue());
231 if (command instanceof OnOffType) {
232 radio.setMuted(OnOffType.ON.equals(command));
236 logger.warn("Ignoring unknown command: {}", command);
238 // make sure that device state is online
239 updateStatus(ThingStatus.ONLINE);
240 } catch (Exception e) {
241 // set device state to offline
242 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());