]> git.basschouten.com Git - openhab-addons.git/blob
8d11b8462924d7cc046607f88ede2c6d89d7c302
[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.velbus.internal.handler;
14
15 import static org.openhab.binding.velbus.internal.VelbusBindingConstants.*;
16
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.time.Instant;
21 import java.time.ZonedDateTime;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.TimeZone;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.velbus.internal.VelbusClockAlarm;
33 import org.openhab.binding.velbus.internal.VelbusClockAlarmConfiguration;
34 import org.openhab.binding.velbus.internal.VelbusPacketInputStream;
35 import org.openhab.binding.velbus.internal.VelbusPacketListener;
36 import org.openhab.binding.velbus.internal.config.VelbusBridgeConfig;
37 import org.openhab.binding.velbus.internal.discovery.VelbusThingDiscoveryService;
38 import org.openhab.binding.velbus.internal.packets.VelbusSetDatePacket;
39 import org.openhab.binding.velbus.internal.packets.VelbusSetDaylightSavingsStatusPacket;
40 import org.openhab.binding.velbus.internal.packets.VelbusSetLocalClockAlarmPacket;
41 import org.openhab.binding.velbus.internal.packets.VelbusSetRealtimeClockPacket;
42 import org.openhab.core.library.types.DecimalType;
43 import org.openhab.core.library.types.OnOffType;
44 import org.openhab.core.thing.Bridge;
45 import org.openhab.core.thing.ChannelUID;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.binding.BaseBridgeHandler;
49 import org.openhab.core.thing.binding.ThingHandlerService;
50 import org.openhab.core.types.Command;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * {@link VelbusBridgeHandler} is an abstract handler for a Velbus interface and connects it to
56  * the framework.
57  *
58  * @author Cedric Boon - Initial contribution
59  * @author Daniel Rosengarten - Add global alarm configuration from bridge (removed from modules), reduces bus flooding
60  *         on alarm value update
61  */
62 @NonNullByDefault
63 public abstract class VelbusBridgeHandler extends BaseBridgeHandler {
64     private final Logger logger = LoggerFactory.getLogger(VelbusBridgeHandler.class);
65
66     private long lastPacketTimeMillis;
67
68     protected @Nullable VelbusPacketListener defaultPacketListener;
69     protected Map<Byte, VelbusPacketListener> packetListeners = new HashMap<>();
70
71     private @NonNullByDefault({}) VelbusBridgeConfig bridgeConfig;
72     private @Nullable ScheduledFuture<?> timeUpdateJob;
73     private @Nullable ScheduledFuture<?> reconnectionHandler;
74
75     private @NonNullByDefault({}) OutputStream outputStream;
76     private @NonNullByDefault({}) VelbusPacketInputStream inputStream;
77
78     private boolean listenerStopped;
79
80     private VelbusClockAlarmConfiguration alarmClockConfiguration = new VelbusClockAlarmConfiguration();
81
82     private long lastUpdateAlarm1TimeMillis;
83     private long lastUpdateAlarm2TimeMillis;
84
85     public VelbusBridgeHandler(Bridge velbusBridge) {
86         super(velbusBridge);
87     }
88
89     @Override
90     public void initialize() {
91         logger.debug("Initializing velbus bridge handler.");
92
93         bridgeConfig = getConfigAs(VelbusBridgeConfig.class);
94
95         connect();
96         initializeTimeUpdate();
97     }
98
99     private void initializeTimeUpdate() {
100         int timeUpdateInterval = bridgeConfig.timeUpdateInterval;
101
102         if (timeUpdateInterval > 0) {
103             startTimeUpdates(timeUpdateInterval);
104         }
105     }
106
107     private void startTimeUpdates(int timeUpdatesInterval) {
108         timeUpdateJob = scheduler.scheduleWithFixedDelay(this::updateDateTime, 0, timeUpdatesInterval,
109                 TimeUnit.MINUTES);
110     }
111
112     private void updateDateTime() {
113         ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.now(), TimeZone.getDefault().toZoneId());
114
115         updateDate(zonedDateTime);
116         updateTime(zonedDateTime);
117         updateDaylightSavingsStatus(zonedDateTime);
118     }
119
120     private void updateTime(ZonedDateTime zonedDateTime) {
121         VelbusSetRealtimeClockPacket packet = new VelbusSetRealtimeClockPacket((byte) 0x00, zonedDateTime);
122
123         byte[] packetBytes = packet.getBytes();
124         this.sendPacket(packetBytes);
125     }
126
127     private void updateDate(ZonedDateTime zonedDateTime) {
128         VelbusSetDatePacket packet = new VelbusSetDatePacket((byte) 0x00, zonedDateTime);
129
130         byte[] packetBytes = packet.getBytes();
131         this.sendPacket(packetBytes);
132     }
133
134     private void updateDaylightSavingsStatus(ZonedDateTime zonedDateTime) {
135         VelbusSetDaylightSavingsStatusPacket packet = new VelbusSetDaylightSavingsStatusPacket((byte) 0x00,
136                 zonedDateTime);
137
138         byte[] packetBytes = packet.getBytes();
139         this.sendPacket(packetBytes);
140     }
141
142     protected void initializeStreams(OutputStream outputStream, InputStream inputStream) {
143         this.outputStream = outputStream;
144         this.inputStream = new VelbusPacketInputStream(inputStream);
145     }
146
147     @Override
148     public void dispose() {
149         final ScheduledFuture<?> timeUpdateJob = this.timeUpdateJob;
150         if (timeUpdateJob != null) {
151             timeUpdateJob.cancel(true);
152         }
153         disconnect();
154     }
155
156     @Override
157     public void handleCommand(ChannelUID channelUID, Command command) {
158         if (isAlarmClockChannel(channelUID)) {
159             byte alarmNumber = determineAlarmNumber(channelUID);
160             VelbusClockAlarm alarmClock = alarmClockConfiguration.getAlarmClock(alarmNumber);
161
162             alarmClock.setLocal(false);
163
164             switch (channelUID.getId()) {
165                 case CHANNEL_BRIDGE_CLOCK_ALARM1_ENABLED:
166                 case CHANNEL_BRIDGE_CLOCK_ALARM2_ENABLED: {
167                     if (command instanceof OnOffType) {
168                         boolean enabled = command == OnOffType.ON;
169                         alarmClock.setEnabled(enabled);
170                     }
171                     break;
172                 }
173                 case CHANNEL_BRIDGE_CLOCK_ALARM1_WAKEUP_HOUR:
174                 case CHANNEL_BRIDGE_CLOCK_ALARM2_WAKEUP_HOUR: {
175                     if (command instanceof DecimalType) {
176                         byte wakeupHour = ((DecimalType) command).byteValue();
177                         alarmClock.setWakeupHour(wakeupHour);
178                     }
179                     break;
180                 }
181                 case CHANNEL_BRIDGE_CLOCK_ALARM1_WAKEUP_MINUTE:
182                 case CHANNEL_BRIDGE_CLOCK_ALARM2_WAKEUP_MINUTE: {
183                     if (command instanceof DecimalType) {
184                         byte wakeupMinute = ((DecimalType) command).byteValue();
185                         alarmClock.setWakeupMinute(wakeupMinute);
186                     }
187                     break;
188                 }
189                 case CHANNEL_BRIDGE_CLOCK_ALARM1_BEDTIME_HOUR:
190                 case CHANNEL_BRIDGE_CLOCK_ALARM2_BEDTIME_HOUR: {
191                     if (command instanceof DecimalType) {
192                         byte bedTimeHour = ((DecimalType) command).byteValue();
193                         alarmClock.setBedtimeHour(bedTimeHour);
194                     }
195                     break;
196                 }
197                 case CHANNEL_BRIDGE_CLOCK_ALARM1_BEDTIME_MINUTE:
198                 case CHANNEL_BRIDGE_CLOCK_ALARM2_BEDTIME_MINUTE: {
199                     if (command instanceof DecimalType) {
200                         byte bedTimeMinute = ((DecimalType) command).byteValue();
201                         alarmClock.setBedtimeMinute(bedTimeMinute);
202                     }
203                     break;
204                 }
205             }
206
207             if (alarmNumber == 1) {
208                 lastUpdateAlarm1TimeMillis = System.currentTimeMillis();
209             } else {
210                 lastUpdateAlarm2TimeMillis = System.currentTimeMillis();
211             }
212
213             VelbusSetLocalClockAlarmPacket packet = new VelbusSetLocalClockAlarmPacket((byte) 0x00, alarmNumber,
214                     alarmClock);
215             byte[] packetBytes = packet.getBytes();
216
217             // Schedule the send of the packet to see if there is another update in less than 10 secondes (reduce
218             // flooding of the bus)
219             scheduler.schedule(() -> {
220                 sendAlarmPacket(alarmNumber, packetBytes);
221             }, DELAY_SEND_CLOCK_ALARM_UPDATE, TimeUnit.MILLISECONDS);
222         } else {
223             logger.debug("The command '{}' is not supported by this handler.", command.getClass());
224         }
225     }
226
227     public synchronized void sendAlarmPacket(int alarmNumber, byte[] packetBytes) {
228         long timeSinceLastUpdate;
229
230         if (alarmNumber == 1) {
231             timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateAlarm1TimeMillis;
232         } else {
233             timeSinceLastUpdate = System.currentTimeMillis() - lastUpdateAlarm2TimeMillis;
234         }
235
236         // If a value of the alarm has been updated, discard this old update
237         if (timeSinceLastUpdate < DELAY_SEND_CLOCK_ALARM_UPDATE) {
238             return;
239         }
240
241         sendPacket(packetBytes);
242     }
243
244     public synchronized void sendPacket(byte[] packet) {
245         long currentTimeMillis = System.currentTimeMillis();
246         long timeSinceLastPacket = currentTimeMillis - lastPacketTimeMillis;
247
248         if (timeSinceLastPacket < 60) {
249             // When sending you need a delay of 60ms between each packet (to prevent flooding the VMB1USB).
250             long timeToDelay = 60 - timeSinceLastPacket;
251
252             scheduler.schedule(() -> {
253                 sendPacket(packet);
254             }, timeToDelay, TimeUnit.MILLISECONDS);
255
256             return;
257         }
258
259         writePacket(packet);
260
261         lastPacketTimeMillis = System.currentTimeMillis();
262     }
263
264     private void readPacket(byte[] packet) {
265         byte address = packet[2];
266
267         if (packetListeners.containsKey(address)) {
268             VelbusPacketListener packetListener = packetListeners.get(address);
269             packetListener.onPacketReceived(packet);
270         } else {
271             final VelbusPacketListener defaultPacketListener = this.defaultPacketListener;
272             if (defaultPacketListener != null) {
273                 defaultPacketListener.onPacketReceived(packet);
274             }
275         }
276     }
277
278     protected void readPackets() {
279         if (inputStream == null) {
280             onConnectionLost();
281             return;
282         }
283
284         byte[] packet;
285
286         listenerStopped = false;
287
288         try {
289             while (!listenerStopped & ((packet = inputStream.readPacket()).length > 0)) {
290                 readPacket(packet);
291             }
292         } catch (IOException e) {
293             if (!listenerStopped) {
294                 onConnectionLost();
295             }
296         }
297     }
298
299     private void writePacket(byte[] packet) {
300         if (outputStream == null) {
301             onConnectionLost();
302             return;
303         }
304
305         try {
306             outputStream.write(packet);
307             outputStream.flush();
308         } catch (IOException e) {
309             onConnectionLost();
310         }
311     }
312
313     protected void onConnectionLost() {
314         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
315                 "A network communication error occurred.");
316         disconnect();
317         startReconnectionHandler();
318     }
319
320     /**
321      * Makes a connection to the Velbus system.
322      *
323      * @return True if the connection succeeded, false if the connection did not succeed.
324      */
325     protected abstract boolean connect();
326
327     protected void disconnect() {
328         listenerStopped = true;
329
330         try {
331             if (outputStream != null) {
332                 outputStream.close();
333             }
334         } catch (IOException e) {
335             logger.debug("Error while closing output stream", e);
336         }
337
338         try {
339             if (inputStream != null) {
340                 inputStream.close();
341             }
342         } catch (IOException e) {
343             logger.debug("Error while closing input stream", e);
344         }
345     }
346
347     public void startReconnectionHandler() {
348         final ScheduledFuture<?> reconnectionHandler = this.reconnectionHandler;
349         if (reconnectionHandler == null || reconnectionHandler.isCancelled()) {
350             int reconnectionInterval = bridgeConfig.reconnectionInterval;
351             if (reconnectionInterval > 0) {
352                 this.reconnectionHandler = scheduler.scheduleWithFixedDelay(() -> {
353                     final ScheduledFuture<?> currentReconnectionHandler = this.reconnectionHandler;
354                     if (connect() && currentReconnectionHandler != null) {
355                         currentReconnectionHandler.cancel(false);
356                     }
357                 }, reconnectionInterval, reconnectionInterval, TimeUnit.SECONDS);
358             }
359         }
360     }
361
362     @Override
363     public Collection<Class<? extends ThingHandlerService>> getServices() {
364         return Collections.singleton(VelbusThingDiscoveryService.class);
365     }
366
367     public void setDefaultPacketListener(VelbusPacketListener velbusPacketListener) {
368         defaultPacketListener = velbusPacketListener;
369     }
370
371     public void clearDefaultPacketListener() {
372         defaultPacketListener = null;
373     }
374
375     public void registerPacketListener(byte address, VelbusPacketListener packetListener) {
376         packetListeners.put(Byte.valueOf(address), packetListener);
377     }
378
379     public void unregisterRelayStatusListener(byte address) {
380         packetListeners.remove(Byte.valueOf(address));
381     }
382
383     protected boolean isAlarmClockChannel(ChannelUID channelUID) {
384         switch (channelUID.getId()) {
385             case CHANNEL_BRIDGE_CLOCK_ALARM1_ENABLED:
386             case CHANNEL_BRIDGE_CLOCK_ALARM1_WAKEUP_HOUR:
387             case CHANNEL_BRIDGE_CLOCK_ALARM1_WAKEUP_MINUTE:
388             case CHANNEL_BRIDGE_CLOCK_ALARM1_BEDTIME_HOUR:
389             case CHANNEL_BRIDGE_CLOCK_ALARM1_BEDTIME_MINUTE:
390             case CHANNEL_BRIDGE_CLOCK_ALARM2_ENABLED:
391             case CHANNEL_BRIDGE_CLOCK_ALARM2_WAKEUP_HOUR:
392             case CHANNEL_BRIDGE_CLOCK_ALARM2_WAKEUP_MINUTE:
393             case CHANNEL_BRIDGE_CLOCK_ALARM2_BEDTIME_HOUR:
394             case CHANNEL_BRIDGE_CLOCK_ALARM2_BEDTIME_MINUTE:
395                 return true;
396         }
397         return false;
398     }
399
400     protected byte determineAlarmNumber(ChannelUID channelUID) {
401         switch (channelUID.getId()) {
402             case CHANNEL_BRIDGE_CLOCK_ALARM1_ENABLED:
403             case CHANNEL_BRIDGE_CLOCK_ALARM1_WAKEUP_HOUR:
404             case CHANNEL_BRIDGE_CLOCK_ALARM1_WAKEUP_MINUTE:
405             case CHANNEL_BRIDGE_CLOCK_ALARM1_BEDTIME_HOUR:
406             case CHANNEL_BRIDGE_CLOCK_ALARM1_BEDTIME_MINUTE:
407                 return 1;
408             case CHANNEL_BRIDGE_CLOCK_ALARM2_ENABLED:
409             case CHANNEL_BRIDGE_CLOCK_ALARM2_WAKEUP_HOUR:
410             case CHANNEL_BRIDGE_CLOCK_ALARM2_WAKEUP_MINUTE:
411             case CHANNEL_BRIDGE_CLOCK_ALARM2_BEDTIME_HOUR:
412             case CHANNEL_BRIDGE_CLOCK_ALARM2_BEDTIME_MINUTE:
413                 return 2;
414         }
415
416         throw new IllegalArgumentException("The given channelUID is not an alarm clock channel: " + channelUID);
417     }
418 }