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.milight.internal.handler;
15 import java.io.IOException;
16 import java.net.InetAddress;
17 import java.time.Instant;
18 import java.time.LocalDateTime;
19 import java.time.ZoneId;
20 import java.time.format.DateTimeFormatter;
21 import java.time.format.FormatStyle;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.milight.internal.MilightBindingConstants;
28 import org.openhab.binding.milight.internal.protocol.MilightV6SessionManager;
29 import org.openhab.binding.milight.internal.protocol.MilightV6SessionManager.ISessionState;
30 import org.openhab.binding.milight.internal.protocol.MilightV6SessionManager.SessionState;
31 import org.openhab.core.config.core.Configuration;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
37 * The {@link BridgeV6Handler} is responsible for handling commands, which are
38 * sent to one of the channels.
40 * @author David Graeff - Initial contribution
43 public class BridgeV6Handler extends AbstractBridgeHandler implements ISessionState {
44 private @NonNullByDefault({}) MilightV6SessionManager session;
45 final DateTimeFormatter timeFormat = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
46 private String offlineReason = "";
47 private @Nullable ScheduledFuture<?> scheduledFuture;
49 public BridgeV6Handler(Bridge bridge, int bridgeOffset) {
50 super(bridge, bridgeOffset);
54 * Creates a session manager object and the send queue. The initial IP address may be null
55 * or is not matching with the real IP address of the bridge. The session manager will send
56 * a broadcast packet to find the bridge with the respective bridge ID and will change the
57 * IP address of the send queue object accordingly.
59 * The keep alive timer that is also setup here, will send keep alive packets periodically.
60 * If the bridge doesn't respond anymore (e.g. DHCP IP change), the initial session handshake
61 * starts all over again.
64 protected void startConnectAndKeepAlive() {
65 if (!config.bridgeid.matches("^([0-9A-Fa-f]{12})$")) {
66 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridgeID invalid!");
70 if (config.port == 0) {
71 config.port = MilightBindingConstants.PORT_VER6;
74 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Waiting for session");
75 int refreshTime = Math.max(Math.min(config.refreshTime, MilightV6SessionManager.TIMEOUT_MS), 100);
76 this.session = new MilightV6SessionManager(config.bridgeid, this, address, config.port, refreshTime,
77 new byte[] { (byte) config.passwordByte1, (byte) config.passwordByte2 });
78 session.start().thenAccept(d -> this.socket = d);
82 public void dispose() {
83 stopMakeOfflineTimer();
86 } catch (IOException ignore) {
92 public MilightV6SessionManager getSessionManager() {
97 public void sessionStateChanged(SessionState state, @Nullable InetAddress newAddress) {
98 stopMakeOfflineTimer();
100 case SESSION_VALID_KEEP_ALIVE:
101 preventReinit = true;
102 Instant lastSessionTime = session.getLastSessionValidConfirmation();
103 LocalDateTime date = LocalDateTime.ofInstant(lastSessionTime, ZoneId.systemDefault());
104 updateProperty(MilightBindingConstants.PROPERTY_SESSIONCONFIRMED, date.format(timeFormat));
105 preventReinit = false;
108 if (newAddress == null) {
109 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address received");
112 if (!newAddress.equals(address) || !thing.getStatus().equals(ThingStatus.ONLINE)) {
113 updateStatus(ThingStatus.ONLINE);
114 this.address = newAddress;
115 // As soon as the session is valid, update the user visible configuration of the host IP.
116 Configuration c = editConfiguration();
117 c.put(BridgeHandlerConfig.CONFIG_HOST_NAME, newAddress.getHostAddress());
118 thing.setProperty(MilightBindingConstants.PROPERTY_SESSIONID, session.getSession());
119 thing.setProperty(MilightBindingConstants.PROPERTY_SESSIONCONFIRMED,
120 String.valueOf(session.getLastSessionValidConfirmation()));
121 preventReinit = true;
122 updateConfiguration(c);
123 preventReinit = false;
126 case SESSION_INVALID:
127 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
128 "Session could not be established");
132 // Delay putting the session offline
133 offlineReason = state.name();
134 scheduledFuture = scheduler.schedule(
135 () -> updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, offlineReason),
136 1000, TimeUnit.MILLISECONDS);
141 private void stopMakeOfflineTimer() {
142 ScheduledFuture<?> future = scheduledFuture;
143 if (future != null) {
144 future.cancel(false);
145 scheduledFuture = null;