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.enigma2.internal.handler;
15 import static org.openhab.binding.enigma2.internal.Enigma2BindingConstants.*;
17 import java.time.LocalDateTime;
18 import java.util.Collection;
19 import java.util.Optional;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.enigma2.internal.Enigma2Client;
29 import org.openhab.binding.enigma2.internal.Enigma2Configuration;
30 import org.openhab.binding.enigma2.internal.Enigma2RemoteKey;
31 import org.openhab.binding.enigma2.internal.actions.Enigma2Actions;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.NextPreviousType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.PercentType;
36 import org.openhab.core.library.types.PlayPauseType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.thing.binding.ThingHandlerService;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * The {@link Enigma2Handler} is responsible for handling commands, which are
51 * sent to one of the channels.
53 * @author Guido Dolfen - Initial contribution
56 public class Enigma2Handler extends BaseThingHandler {
57 private final Logger logger = LoggerFactory.getLogger(Enigma2Handler.class);
58 private Enigma2Configuration configuration = new Enigma2Configuration();
59 private Optional<Enigma2Client> enigma2Client = Optional.empty();
60 private @Nullable ScheduledFuture<?> refreshJob;
61 private LocalDateTime lastAnswerTime = LocalDateTime.now();
63 public Enigma2Handler(Thing thing) {
68 public void initialize() {
69 configuration = getConfigAs(Enigma2Configuration.class);
70 if (configuration.host.isEmpty()) {
71 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "host must not be empty");
72 } else if (configuration.timeout <= 0 || configuration.timeout > 300) {
73 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
74 "timeout must be between 0 and 300 seconds");
75 } else if (configuration.refreshInterval <= 0 || configuration.refreshInterval > 3600) {
76 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
77 "refreshInterval must be between 0 and 3600 seconds");
79 enigma2Client = Optional.of(new Enigma2Client(configuration.host, configuration.user, configuration.password,
80 configuration.timeout));
81 refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 2, configuration.refreshInterval,
85 private void refresh() {
86 getEnigma2Client().ifPresent(client -> {
87 boolean online = client.refresh();
89 updateStatus(ThingStatus.ONLINE);
90 updateState(CHANNEL_POWER, OnOffType.from(client.isPower()));
91 updateState(CHANNEL_MUTE, OnOffType.from(client.isMute()));
92 updateState(CHANNEL_VOLUME, new PercentType(client.getVolume()));
93 updateState(CHANNEL_CHANNEL, new StringType(client.getChannel()));
94 updateState(CHANNEL_TITLE, new StringType(client.getTitle()));
95 updateState(CHANNEL_DESCRIPTION, new StringType(client.getDescription()));
96 if (lastAnswerTime.isBefore(client.getLastAnswerTime())) {
97 lastAnswerTime = client.getLastAnswerTime();
98 updateState(CHANNEL_ANSWER, new StringType(client.getAnswer()));
101 updateStatus(ThingStatus.OFFLINE);
107 public void dispose() {
108 ScheduledFuture<?> job = this.refreshJob;
112 this.refreshJob = null;
116 public void handleCommand(ChannelUID channelUID, Command command) {
117 logger.debug("handleCommand({},{})", channelUID, command);
118 getEnigma2Client().ifPresent(client -> {
119 switch (channelUID.getId()) {
121 handlePower(channelUID, command, client);
123 case CHANNEL_CHANNEL:
124 handleChannel(channelUID, command, client);
126 case CHANNEL_MEDIA_PLAYER:
127 handleMediaPlayer(channelUID, command);
129 case CHANNEL_MEDIA_STOP:
130 handleMediaStop(channelUID, command);
133 handleMute(channelUID, command, client);
136 handleVolume(channelUID, command, client);
139 handleTitle(channelUID, command, client);
141 case CHANNEL_DESCRIPTION:
142 handleDescription(channelUID, command, client);
145 handleAnswer(channelUID, command, client);
148 logger.debug("Channel {} is not supported", channelUID);
154 private void handleVolume(ChannelUID channelUID, Command command, Enigma2Client client) {
155 if (command instanceof RefreshType) {
156 client.refreshVolume();
157 updateState(channelUID, new PercentType(client.getVolume()));
158 } else if (command instanceof PercentType percentCommand) {
159 client.setVolume(percentCommand.intValue());
160 } else if (command instanceof DecimalType decimalCommand) {
161 client.setVolume(decimalCommand.intValue());
163 logger.info("Channel {} only accepts PercentType, DecimalType, RefreshType. Type was {}.", channelUID,
168 private void handleMute(ChannelUID channelUID, Command command, Enigma2Client client) {
169 if (command instanceof RefreshType) {
170 client.refreshVolume();
171 updateState(channelUID, OnOffType.from(client.isMute()));
172 } else if (OnOffType.ON.equals(command)) {
173 client.setMute(true);
174 } else if (OnOffType.OFF.equals(command)) {
175 client.setMute(false);
177 logger.info("Channel {} only accepts OnOffType, RefreshType. Type was {}.", channelUID, command.getClass());
181 private void handleAnswer(ChannelUID channelUID, Command command, Enigma2Client client) {
182 if (command instanceof RefreshType) {
183 client.refreshAnswer();
184 if (lastAnswerTime.isBefore(client.getLastAnswerTime())) {
185 lastAnswerTime = client.getLastAnswerTime();
186 updateState(channelUID, new StringType(client.getAnswer()));
189 logger.info("Channel {} only accepts RefreshType. Type was {}.", channelUID, command.getClass());
193 private void handleMediaStop(ChannelUID channelUID, Command command) {
194 if (command instanceof RefreshType) {
196 } else if (command instanceof OnOffType) {
197 sendRcCommand(Enigma2RemoteKey.STOP);
199 logger.info("Channel {} only accepts OnOffType, RefreshType. Type was {}.", channelUID, command.getClass());
203 private void handleMediaPlayer(ChannelUID channelUID, Command command) {
204 if (RefreshType.REFRESH == command) {
206 } else if (PlayPauseType.PLAY == command) {
207 sendRcCommand(Enigma2RemoteKey.PLAY);
208 } else if (PlayPauseType.PAUSE == command) {
209 sendRcCommand(Enigma2RemoteKey.PAUSE);
210 } else if (NextPreviousType.NEXT == command) {
211 sendRcCommand(Enigma2RemoteKey.FAST_FORWARD);
212 } else if (NextPreviousType.PREVIOUS == command) {
213 sendRcCommand(Enigma2RemoteKey.FAST_BACKWARD);
215 logger.info("Channel {} only accepts PlayPauseType, NextPreviousType, RefreshType. Type was {}.",
216 channelUID, command.getClass());
220 private void handleChannel(ChannelUID channelUID, Command command, Enigma2Client client) {
221 if (command instanceof RefreshType) {
222 client.refreshChannel();
223 updateState(channelUID, new StringType(client.getChannel()));
224 } else if (command instanceof StringType) {
225 client.setChannel(command.toString());
227 logger.info("Channel {} only accepts StringType, RefreshType. Type was {}.", channelUID,
232 private void handleTitle(ChannelUID channelUID, Command command, Enigma2Client client) {
233 if (command instanceof RefreshType) {
235 updateState(channelUID, new StringType(client.getTitle()));
237 logger.info("Channel {} only accepts RefreshType. Type was {}.", channelUID, command.getClass());
241 private void handleDescription(ChannelUID channelUID, Command command, Enigma2Client client) {
242 if (command instanceof RefreshType) {
244 updateState(channelUID, new StringType(client.getDescription()));
246 logger.info("Channel {} only accepts RefreshType. Type was {}.", channelUID, command.getClass());
250 private void handlePower(ChannelUID channelUID, Command command, Enigma2Client client) {
251 if (RefreshType.REFRESH == command) {
252 client.refreshPower();
253 updateState(channelUID, OnOffType.from(client.isPower()));
254 } else if (OnOffType.ON == command) {
255 client.setPower(true);
256 } else if (OnOffType.OFF == command) {
257 client.setPower(false);
259 logger.info("Channel {} only accepts OnOffType, RefreshType. Type was {}.", channelUID, command.getClass());
263 public void sendRcCommand(String rcButton) {
264 logger.debug("sendRcCommand({})", rcButton);
266 Enigma2RemoteKey remoteKey = Enigma2RemoteKey.valueOf(rcButton);
267 sendRcCommand(remoteKey);
268 } catch (IllegalArgumentException ex) {
269 logger.warn("{} is not a valid value for button - available are: {}", rcButton,
270 Stream.of(Enigma2RemoteKey.values()).map(b -> b.name()).collect(Collectors.joining(", ")));
274 private void sendRcCommand(Enigma2RemoteKey remoteKey) {
275 getEnigma2Client().ifPresent(client -> client.sendRcCommand(remoteKey.getValue()));
278 public void sendInfo(int timeout, String text) {
279 getEnigma2Client().ifPresent(client -> client.sendInfo(timeout, text));
282 public void sendWarning(int timeout, String text) {
283 getEnigma2Client().ifPresent(client -> client.sendWarning(timeout, text));
286 public void sendError(int timeout, String text) {
287 getEnigma2Client().ifPresent(client -> client.sendError(timeout, text));
290 public void sendQuestion(int timeout, String text) {
291 getEnigma2Client().ifPresent(client -> client.sendQuestion(timeout, text));
295 public Collection<Class<? extends ThingHandlerService>> getServices() {
296 return Set.of(Enigma2Actions.class);
300 * Getter for Test-Injection
302 * @return Enigma2Client.
304 Optional<Enigma2Client> getEnigma2Client() {
305 return enigma2Client;