2 * Copyright (c) 2010-2022 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.energenie.internal.handler;
15 import static org.openhab.binding.energenie.internal.EnergenieBindingConstants.*;
17 import java.io.IOException;
18 import java.net.UnknownHostException;
19 import java.time.Duration;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.regex.Matcher;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.energenie.internal.EnergenieProtocolEnum;
27 import org.openhab.binding.energenie.internal.config.EnergenieConfiguration;
28 import org.openhab.core.cache.ExpiringCache;
29 import org.openhab.core.library.types.OnOffType;
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.thing.util.ThingHandlerHelper;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.RefreshType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * The {@link EnergenieHandler} is responsible for handling commands, which are
43 * sent to one of the channels.
45 * @author Hans-Jörg Merk - Initial contribution
48 public class EnergenieHandler extends BaseThingHandler {
50 private static final String CHANNEL_SOCKET_PREFIX = "socket";
51 private final Logger logger = LoggerFactory.getLogger(EnergenieHandler.class);
54 * Use cache for refresh command to not update again when call is made within 4 seconds of previous call.
56 private final ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofSeconds(5), this::refreshState);
57 private final String statusOn;
59 private @Nullable EnergenieSocket energenieSocket;
60 private @Nullable ScheduledFuture<?> refreshJob;
62 private int refreshInterval;
63 private @Nullable String host;
65 public EnergenieHandler(final Thing thing, final EnergenieProtocolEnum protocol) {
67 this.statusOn = protocol.getStatusOn();
71 public void handleCommand(final ChannelUID channelUID, final Command command) {
72 if (command instanceof RefreshType) {
73 refreshCache.getValue();
74 } else if (command instanceof OnOffType) {
75 final byte[] ctrl = { DONT_SWITCH, DONT_SWITCH, DONT_SWITCH, DONT_SWITCH };
76 final Matcher matcher = CHANNEL_SOCKET.matcher(channelUID.getId());
79 boolean found = false;
82 final int index = Integer.parseInt(matcher.group(1)) - 1;
84 if (index >= 0 && index < SOCKET_COUNT) {
85 ctrl[index] = OnOffType.ON.equals(command) ? SWITCH_ON : SWITCH_OFF;
86 stateUpdate(energenieSocket.sendCommand(ctrl));
91 logger.debug("Invalid channel id {}, should be value between 1 and {}", channelUID, SOCKET_COUNT);
93 } catch (final IOException e) {
94 updateStatus(ThingStatus.OFFLINE);
95 logger.debug("Couldn't get I/O for the connection to: {}:{}.", host, TCP_PORT, e);
101 public void initialize() {
102 final EnergenieConfiguration config = getConfigAs(EnergenieConfiguration.class);
104 if (!config.host.isEmpty() && !config.password.isEmpty()) {
105 refreshInterval = EnergenieConfiguration.DEFAULT_REFRESH_INTERVAL;
107 logger.debug("Initializing EnergenieHandler for Host '{}'", config.host);
108 energenieSocket = new EnergenieSocket(config.host, config.password);
110 updateStatus(ThingStatus.UNKNOWN);
111 refreshJob = scheduler.scheduleWithFixedDelay(this::refreshState, 6, refreshInterval, TimeUnit.SECONDS);
113 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
114 "Can not access device , IP-Address or password not set");
119 public void dispose() {
120 logger.debug("EnergenieHandler disposed.");
121 final ScheduledFuture<?> refreshJob = this.refreshJob;
123 if (refreshJob != null) {
124 refreshJob.cancel(true);
125 this.refreshJob = null;
129 private boolean refreshState() {
130 final EnergenieSocket socket = this.energenieSocket;
132 if (socket != null) {
134 stateUpdate(socket.retrieveStatus());
135 if (thing.getStatus() != ThingStatus.ONLINE && ThingHandlerHelper.isHandlerInitialized(thing)) {
136 updateStatus(ThingStatus.ONLINE);
139 } catch (final UnknownHostException e) {
140 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
141 "Can't find host: " + e.getMessage());
142 } catch (final IOException e) {
143 logger.debug("Couldn't get I/O for the connection to: {}:{}.", host, TCP_PORT, e);
144 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
145 "Couldn't get I/O for the connection");
146 } catch (final RuntimeException e) {
147 logger.debug("Unexpected error", e);
148 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, e.getMessage());
154 public void stateUpdate(final byte[] status) {
155 for (int i = 0; i < 4; i++) {
156 final String socket = CHANNEL_SOCKET_PREFIX + (i + 1);
157 final String stringStatus = String.format("0x%02x", status[i]);
158 updateState(socket, OnOffType.from(stringStatus.equals(statusOn)));