]> git.basschouten.com Git - openhab-addons.git/blob
fc706c934ccc044f50fcc11619df040a3b275380
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.herzborg.internal;
14
15 import static org.openhab.binding.herzborg.internal.HerzborgBindingConstants.*;
16
17 import java.io.IOException;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import javax.xml.bind.DatatypeConverter;
22
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;
46
47 /**
48  * The {@link CurtainHandler} is responsible for handling commands, which are
49  * sent to one of the channels.
50  *
51  * @author Pavel Fedin - Initial contribution
52  */
53 @NonNullByDefault
54 public class CurtainHandler extends BaseThingHandler {
55     private final Logger logger = LoggerFactory.getLogger(CurtainHandler.class);
56
57     private CurtainConfiguration config = new CurtainConfiguration();
58     private @Nullable ScheduledFuture<?> pollFuture;
59     private @Nullable Bus bus;
60
61     public CurtainHandler(Thing thing) {
62         super(thing);
63     }
64
65     @Override
66     public void handleCommand(ChannelUID channelUID, Command command) {
67         String ch = channelUID.getId();
68         Packet pkt = null;
69
70         switch (ch) {
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());
79                 }
80                 break;
81             case CHANNEL_REVERSE:
82                 if (command instanceof OnOffType) {
83                     pkt = buildPacket(Function.WRITE, DataAddress.DEFAULT_DIR, command.equals(OnOffType.ON) ? 1 : 0);
84                 }
85                 break;
86             case CHANNEL_HAND_START:
87                 if (command instanceof OnOffType) {
88                     pkt = buildPacket(Function.WRITE, DataAddress.HAND_START, command.equals(OnOffType.ON) ? 0 : 1);
89                 }
90                 break;
91             case CHANNEL_EXT_SWITCH:
92                 if (command instanceof StringType) {
93                     pkt = buildPacket(Function.WRITE, DataAddress.EXT_SWITCH, Byte.valueOf(command.toString()));
94                 }
95                 break;
96             case CHANNEL_HV_SWITCH:
97                 if (command instanceof StringType) {
98                     pkt = buildPacket(Function.WRITE, DataAddress.EXT_HV_SWITCH, Byte.valueOf(command.toString()));
99                 }
100                 break;
101         }
102
103         if (pkt != null) {
104             final Packet p = pkt;
105             scheduler.schedule(() -> {
106                 Packet reply = doPacket(p);
107
108                 if (reply != null) {
109                     logger.trace("Function {} addr {} reply {}", p.getFunction(), p.getDataAddress(),
110                             DatatypeConverter.printHexBinary(reply.getBuffer()));
111                 }
112             }, 0, TimeUnit.MILLISECONDS);
113         }
114     }
115
116     private Packet buildPacket(byte function, byte data_addr) {
117         return new Packet((short) config.address, function, data_addr);
118     }
119
120     private Packet buildPacket(byte function, byte data_addr, byte value) {
121         return new Packet((short) config.address, function, data_addr, value);
122     }
123
124     private Packet buildPacket(byte function, byte data_addr, int value) {
125         return buildPacket(function, data_addr, (byte) value);
126     }
127
128     @Override
129     public void initialize() {
130         Bridge bridge = getBridge();
131
132         if (bridge == null) {
133             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "Bridge not present");
134             return;
135         }
136
137         BridgeHandler handler = bridge.getHandler();
138
139         if (handler == null) {
140             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "Bridge has no handler");
141             return;
142         }
143
144         bus = ((BusHandler) handler).getBus();
145         config = getConfigAs(CurtainConfiguration.class);
146
147         updateStatus(ThingStatus.UNKNOWN);
148         logger.trace("Successfully initialized, starting poll");
149         pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, config.pollInterval, TimeUnit.SECONDS);
150     }
151
152     @Override
153     public void dispose() {
154         stopPoll();
155     }
156
157     private void stopPoll() {
158         ScheduledFuture<?> poll = pollFuture;
159         pollFuture = null;
160
161         if (poll != null) {
162             poll.cancel(true);
163         }
164     }
165
166     private @Nullable synchronized Packet doPacket(Packet pkt) {
167         Bus bus = this.bus;
168
169         if (bus == null) {
170             // This is an impossible situation but Eclipse forces us to handle it
171             logger.warn("No Bridge sending commands");
172             return null;
173         }
174
175         try {
176             Packet reply = bus.doPacket(pkt);
177
178             if (reply == null) {
179                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
180                 return null;
181             }
182
183             if (reply.isValid()) {
184                 updateStatus(ThingStatus.ONLINE);
185                 return reply;
186             } else {
187                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
188                         "Invalid response received: " + DatatypeConverter.printHexBinary(reply.getBuffer()));
189                 bus.flush();
190             }
191
192         } catch (IOException e) {
193             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
194         }
195
196         return null;
197     }
198
199     private void poll() {
200         Packet reply = doPacket(buildPacket(Function.READ, DataAddress.POSITION, 4));
201
202         if (reply != null) {
203             byte position = reply.getData(0);
204             byte reverse = reply.getData(1);
205             byte handStart = reply.getData(2);
206             byte mode = reply.getData(3);
207
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, reverse != 0 ? OnOffType.ON : OnOffType.OFF);
212             updateState(CHANNEL_HAND_START, handStart == 0 ? OnOffType.ON : OnOffType.OFF);
213             updateState(CHANNEL_MODE, new StringType(String.valueOf(mode)));
214         }
215
216         Packet extReply = doPacket(buildPacket(Function.READ, DataAddress.EXT_SWITCH, 2));
217
218         if (extReply != null) {
219             byte extSwitch = extReply.getData(0);
220             byte hvSwitch = extReply.getData(1);
221
222             updateState(CHANNEL_EXT_SWITCH, new StringType(String.valueOf(extSwitch)));
223             updateState(CHANNEL_HV_SWITCH, new StringType(String.valueOf(hvSwitch)));
224         }
225     }
226 }