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.herzborg.internal;
15 import static org.openhab.binding.herzborg.internal.HerzborgBindingConstants.*;
17 import java.io.IOException;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
21 import javax.xml.bind.DatatypeConverter;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.ControlAddress;
26 import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.DataAddress;
27 import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.Function;
28 import org.openhab.binding.herzborg.internal.dto.HerzborgProtocol.Packet;
29 import org.openhab.core.library.types.DecimalType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StopMoveType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.library.types.UpDownType;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.thing.binding.BridgeHandler;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.UnDefType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * The {@link CurtainHandler} is responsible for handling commands, which are
49 * sent to one of the channels.
51 * @author Pavel Fedin - Initial contribution
54 public class CurtainHandler extends BaseThingHandler {
55 private final Logger logger = LoggerFactory.getLogger(CurtainHandler.class);
57 private CurtainConfiguration config = new CurtainConfiguration();
58 private @Nullable ScheduledFuture<?> pollFuture;
59 private @Nullable Bus bus;
61 public CurtainHandler(Thing thing) {
66 public void handleCommand(ChannelUID channelUID, Command command) {
67 String ch = channelUID.getId();
71 case CHANNEL_POSITION:
72 if (command instanceof UpDownType) {
73 pkt = buildPacket(Function.CONTROL,
74 (command == UpDownType.UP) ? ControlAddress.OPEN : ControlAddress.CLOSE);
75 } else if (command instanceof StopMoveType) {
76 pkt = buildPacket(Function.CONTROL, ControlAddress.STOP);
77 } else if (command instanceof DecimalType decimalCommand) {
78 pkt = buildPacket(Function.CONTROL, ControlAddress.PERCENT, decimalCommand.byteValue());
82 if (command instanceof OnOffType) {
83 pkt = buildPacket(Function.WRITE, DataAddress.DEFAULT_DIR, command.equals(OnOffType.ON) ? 1 : 0);
86 case CHANNEL_HAND_START:
87 if (command instanceof OnOffType) {
88 pkt = buildPacket(Function.WRITE, DataAddress.HAND_START, command.equals(OnOffType.ON) ? 0 : 1);
91 case CHANNEL_EXT_SWITCH:
92 if (command instanceof StringType) {
93 pkt = buildPacket(Function.WRITE, DataAddress.EXT_SWITCH, Byte.valueOf(command.toString()));
96 case CHANNEL_HV_SWITCH:
97 if (command instanceof StringType) {
98 pkt = buildPacket(Function.WRITE, DataAddress.EXT_HV_SWITCH, Byte.valueOf(command.toString()));
104 final Packet p = pkt;
105 scheduler.schedule(() -> {
106 Packet reply = doPacket(p);
109 logger.trace("Function {} addr {} reply {}", p.getFunction(), p.getDataAddress(),
110 DatatypeConverter.printHexBinary(reply.getBuffer()));
112 }, 0, TimeUnit.MILLISECONDS);
116 private Packet buildPacket(byte function, byte data_addr) {
117 return new Packet((short) config.address, function, data_addr);
120 private Packet buildPacket(byte function, byte data_addr, byte value) {
121 return new Packet((short) config.address, function, data_addr, value);
124 private Packet buildPacket(byte function, byte data_addr, int value) {
125 return buildPacket(function, data_addr, (byte) value);
129 public void initialize() {
130 Bridge bridge = getBridge();
132 if (bridge == null) {
133 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "Bridge not present");
137 BridgeHandler handler = bridge.getHandler();
139 if (handler == null) {
140 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "Bridge has no handler");
144 bus = ((BusHandler) handler).getBus();
145 config = getConfigAs(CurtainConfiguration.class);
147 updateStatus(ThingStatus.UNKNOWN);
148 logger.trace("Successfully initialized, starting poll");
149 pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, config.pollInterval, TimeUnit.SECONDS);
153 public void dispose() {
157 private void stopPoll() {
158 ScheduledFuture<?> poll = pollFuture;
166 private @Nullable synchronized Packet doPacket(Packet pkt) {
170 // This is an impossible situation but Eclipse forces us to handle it
171 logger.warn("No Bridge sending commands");
176 Packet reply = bus.doPacket(pkt);
179 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
183 if (reply.isValid()) {
184 updateStatus(ThingStatus.ONLINE);
187 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
188 "Invalid response received: " + DatatypeConverter.printHexBinary(reply.getBuffer()));
192 } catch (IOException e) {
193 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
199 private void poll() {
200 Packet reply = doPacket(buildPacket(Function.READ, DataAddress.POSITION, 4));
203 byte position = reply.getData(0);
204 byte reverse = reply.getData(1);
205 byte handStart = reply.getData(2);
206 byte mode = reply.getData(3);
208 // If calibration has been lost, position is reported as -1.
209 updateState(CHANNEL_POSITION,
210 (position > 100 || position < 0) ? UnDefType.UNDEF : new PercentType(position));
211 updateState(CHANNEL_REVERSE, OnOffType.from(reverse != 0));
212 updateState(CHANNEL_HAND_START, OnOffType.from(handStart == 0));
213 updateState(CHANNEL_MODE, new StringType(String.valueOf(mode)));
216 Packet extReply = doPacket(buildPacket(Function.READ, DataAddress.EXT_SWITCH, 2));
218 if (extReply != null) {
219 byte extSwitch = extReply.getData(0);
220 byte hvSwitch = extReply.getData(1);
222 updateState(CHANNEL_EXT_SWITCH, new StringType(String.valueOf(extSwitch)));
223 updateState(CHANNEL_HV_SWITCH, new StringType(String.valueOf(hvSwitch)));