2 * Copyright (c) 2010-2021 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.velbus.internal.handler;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.time.Instant;
19 import java.time.ZonedDateTime;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashMap;
24 import java.util.TimeZone;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.TimeUnit;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.velbus.internal.VelbusPacketInputStream;
31 import org.openhab.binding.velbus.internal.VelbusPacketListener;
32 import org.openhab.binding.velbus.internal.config.VelbusBridgeConfig;
33 import org.openhab.binding.velbus.internal.discovery.VelbusThingDiscoveryService;
34 import org.openhab.binding.velbus.internal.packets.VelbusSetDatePacket;
35 import org.openhab.binding.velbus.internal.packets.VelbusSetDaylightSavingsStatusPacket;
36 import org.openhab.binding.velbus.internal.packets.VelbusSetRealtimeClockPacket;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.binding.BaseBridgeHandler;
42 import org.openhab.core.thing.binding.ThingHandlerService;
43 import org.openhab.core.types.Command;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * {@link VelbusBridgeHandler} is an abstract handler for a Velbus interface and connects it to
51 * @author Cedric Boon - Initial contribution
54 public abstract class VelbusBridgeHandler extends BaseBridgeHandler {
55 private final Logger logger = LoggerFactory.getLogger(VelbusBridgeHandler.class);
57 private long lastPacketTimeMillis;
59 protected @Nullable VelbusPacketListener defaultPacketListener;
60 protected Map<Byte, VelbusPacketListener> packetListeners = new HashMap<>();
62 private @NonNullByDefault({}) VelbusBridgeConfig bridgeConfig;
63 private @Nullable ScheduledFuture<?> timeUpdateJob;
64 private @Nullable ScheduledFuture<?> reconnectionHandler;
66 private @NonNullByDefault({}) OutputStream outputStream;
67 private @NonNullByDefault({}) VelbusPacketInputStream inputStream;
69 private boolean listenerStopped;
71 public VelbusBridgeHandler(Bridge velbusBridge) {
76 public void initialize() {
77 logger.debug("Initializing velbus bridge handler.");
79 bridgeConfig = getConfigAs(VelbusBridgeConfig.class);
82 initializeTimeUpdate();
85 private void initializeTimeUpdate() {
86 int timeUpdateInterval = bridgeConfig.timeUpdateInterval;
88 if (timeUpdateInterval > 0) {
89 startTimeUpdates(timeUpdateInterval);
93 private void startTimeUpdates(int timeUpdatesInterval) {
94 timeUpdateJob = scheduler.scheduleWithFixedDelay(this::updateDateTime, 0, timeUpdatesInterval,
98 private void updateDateTime() {
99 ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.now(), TimeZone.getDefault().toZoneId());
101 updateDate(zonedDateTime);
102 updateTime(zonedDateTime);
103 updateDaylightSavingsStatus(zonedDateTime);
106 private void updateTime(ZonedDateTime zonedDateTime) {
107 VelbusSetRealtimeClockPacket packet = new VelbusSetRealtimeClockPacket((byte) 0x00, zonedDateTime);
109 byte[] packetBytes = packet.getBytes();
110 this.sendPacket(packetBytes);
113 private void updateDate(ZonedDateTime zonedDateTime) {
114 VelbusSetDatePacket packet = new VelbusSetDatePacket((byte) 0x00, zonedDateTime);
116 byte[] packetBytes = packet.getBytes();
117 this.sendPacket(packetBytes);
120 private void updateDaylightSavingsStatus(ZonedDateTime zonedDateTime) {
121 VelbusSetDaylightSavingsStatusPacket packet = new VelbusSetDaylightSavingsStatusPacket((byte) 0x00,
124 byte[] packetBytes = packet.getBytes();
125 this.sendPacket(packetBytes);
128 protected void initializeStreams(OutputStream outputStream, InputStream inputStream) {
129 this.outputStream = outputStream;
130 this.inputStream = new VelbusPacketInputStream(inputStream);
134 public void dispose() {
135 final ScheduledFuture<?> timeUpdateJob = this.timeUpdateJob;
136 if (timeUpdateJob != null) {
137 timeUpdateJob.cancel(true);
143 public void handleCommand(ChannelUID channelUID, Command command) {
144 // There is nothing to handle in the bridge handler
147 public synchronized void sendPacket(byte[] packet) {
148 long currentTimeMillis = System.currentTimeMillis();
149 long timeSinceLastPacket = currentTimeMillis - lastPacketTimeMillis;
151 if (timeSinceLastPacket < 60) {
152 // When sending you need a delay of 60ms between each packet (to prevent flooding the VMB1USB).
153 long timeToDelay = 60 - timeSinceLastPacket;
155 scheduler.schedule(() -> {
157 }, timeToDelay, TimeUnit.MILLISECONDS);
164 lastPacketTimeMillis = System.currentTimeMillis();
167 private void readPacket(byte[] packet) {
168 byte address = packet[2];
170 if (packetListeners.containsKey(address)) {
171 VelbusPacketListener packetListener = packetListeners.get(address);
172 packetListener.onPacketReceived(packet);
174 final VelbusPacketListener defaultPacketListener = this.defaultPacketListener;
175 if (defaultPacketListener != null) {
176 defaultPacketListener.onPacketReceived(packet);
181 protected void readPackets() {
182 if (inputStream == null) {
189 listenerStopped = false;
192 while (!listenerStopped & ((packet = inputStream.readPacket()).length > 0)) {
195 } catch (IOException e) {
196 if (!listenerStopped) {
202 private void writePacket(byte[] packet) {
203 if (outputStream == null) {
209 outputStream.write(packet);
210 outputStream.flush();
211 } catch (IOException e) {
216 protected void onConnectionLost() {
217 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
218 "A network communication error occurred.");
220 startReconnectionHandler();
224 * Makes a connection to the Velbus system.
226 * @return True if the connection succeeded, false if the connection did not succeed.
228 protected abstract boolean connect();
230 protected void disconnect() {
231 listenerStopped = true;
234 if (outputStream != null) {
235 outputStream.close();
237 } catch (IOException e) {
238 logger.debug("Error while closing output stream", e);
242 if (inputStream != null) {
245 } catch (IOException e) {
246 logger.debug("Error while closing input stream", e);
250 public void startReconnectionHandler() {
251 final ScheduledFuture<?> reconnectionHandler = this.reconnectionHandler;
252 if (reconnectionHandler == null || reconnectionHandler.isCancelled()) {
253 int reconnectionInterval = bridgeConfig.reconnectionInterval;
254 if (reconnectionInterval > 0) {
255 this.reconnectionHandler = scheduler.scheduleWithFixedDelay(() -> {
256 final ScheduledFuture<?> currentReconnectionHandler = this.reconnectionHandler;
257 if (connect() && currentReconnectionHandler != null) {
258 currentReconnectionHandler.cancel(false);
260 }, reconnectionInterval, reconnectionInterval, TimeUnit.SECONDS);
266 public Collection<Class<? extends ThingHandlerService>> getServices() {
267 return Collections.singleton(VelbusThingDiscoveryService.class);
270 public void setDefaultPacketListener(VelbusPacketListener velbusPacketListener) {
271 defaultPacketListener = velbusPacketListener;
274 public void clearDefaultPacketListener() {
275 defaultPacketListener = null;
278 public void registerPacketListener(byte address, VelbusPacketListener packetListener) {
279 packetListeners.put(Byte.valueOf(address), packetListener);
282 public void unregisterRelayStatusListener(byte address) {
283 packetListeners.remove(Byte.valueOf(address));